Make "Mark as Ineligible" reversable and make this feature available for mentors...
[Melange.git] / app / soc / views / models / student_proposal.py
blob0b579136b73db3c066e46360ad61b370356c025a
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
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 import organization as org_logic
35 from soc.logic.models import org_admin as org_admin_logic
36 from soc.logic.models import student as student_logic
37 from soc.logic.models import user as user_logic
38 from soc.views import helper
39 from soc.views import out_of_band
40 from soc.views.helper import access
41 from soc.views.helper import decorators
42 from soc.views.helper import dynaform
43 from soc.views.helper import params as params_helper
44 from soc.views.helper import redirects
45 from soc.views.helper import responses
46 from soc.views.helper import widgets
47 from soc.views.models import base
48 from soc.views.models import student as student_view
50 import soc.logic.models.student_proposal
53 class View(base.View):
54 """View methods for the Student Proposal model.
55 """
57 def __init__(self, params=None):
58 """Defines the fields and methods required for the base View class
59 to provide the user with list, public, create, edit and delete views.
61 Params:
62 params: a dict with params for this View
63 """
65 rights = access.Checker(params)
66 rights['create'] = ['checkIsDeveloper']
67 rights['edit'] = [('checkCanStudentPropose', ['scope_path', False]),
68 ('checkRoleAndStatusForStudentProposal',
69 [['proposer'], ['active'], ['new', 'pending']])]
70 rights['delete'] = ['checkIsDeveloper']
71 rights['show'] = [
72 ('checkRoleAndStatusForStudentProposal',
73 [['proposer', 'org_admin', 'mentor', 'host'],
74 ['active', 'inactive'],
75 ['new', 'pending', 'accepted', 'rejected']])]
76 rights['list'] = ['checkIsDeveloper']
77 rights['list_orgs'] = [
78 ('checkIsStudent', ['scope_path', ['active']]),
79 ('checkCanStudentPropose', ['scope_path', False])]
80 rights['list_self'] = [
81 ('checkIsStudent', ['scope_path', ['active', 'inactive']])]
82 rights['apply'] = [
83 ('checkIsStudent', ['scope_path', ['active']]),
84 ('checkCanStudentPropose', ['scope_path', True])]
85 rights['review'] = [('checkRoleAndStatusForStudentProposal',
86 [['org_admin', 'mentor', 'host'],
87 ['active'], ['new', 'pending', 'invalid']])]
89 new_params = {}
90 new_params['logic'] = soc.logic.models.student_proposal.logic
91 new_params['rights'] = rights
92 new_params['name'] = "Student Proposal"
93 new_params['url_name'] = "student_proposal"
94 new_params['sidebar_grouping'] = 'Students'
96 new_params['scope_view'] = student_view
97 new_params['scope_redirect'] = redirects.getCreateRedirect
99 new_params['no_create_with_key_fields'] = True
101 patterns = [
102 (r'^%(url_name)s/(?P<access_type>apply)/%(scope)s$',
103 'soc.views.models.%(module_name)s.apply',
104 'Create a new %(name)s'),
105 (r'^%(url_name)s/(?P<access_type>list_self)/%(scope)s$',
106 'soc.views.models.%(module_name)s.list_self',
107 'List my %(name_plural)s'),
108 (r'^%(url_name)s/(?P<access_type>list_orgs)/%(scope)s$',
109 'soc.views.models.%(module_name)s.list_orgs',
110 'List my %(name_plural)s'),
111 (r'^%(url_name)s/(?P<access_type>review)/%(key_fields)s$',
112 'soc.views.models.%(module_name)s.review',
113 'Review %(name)s'),
116 new_params['extra_django_patterns'] = patterns
118 new_params['extra_dynaexclude'] = ['org', 'program', 'score',
119 'status', 'mentor', 'link_id',
120 'possible_mentors']
122 new_params['create_extra_dynaproperties'] = {
123 'content': forms.fields.CharField(required=True,
124 widget=widgets.FullTinyMCE(attrs={'rows': 25, 'cols': 100})),
125 'scope_path': forms.CharField(widget=forms.HiddenInput,
126 required=True),
127 'organization': forms.CharField(label='Organization Link ID',
128 required=True),
129 'clean_abstract': cleaning.clean_content_length('abstract'),
130 'clean_content': cleaning.clean_html_content('content'),
131 'clean_organization': cleaning.clean_link_id('organization'),
132 'clean_additional_info': cleaning.clean_url('additional_info'),
133 'clean': cleaning.validate_student_proposal('organization',
134 'scope_path', student_logic, org_logic),
137 new_params['edit_extra_dynaproperties'] = {
138 'organization': forms.CharField(label='Organization Link ID',
139 widget=widgets.ReadOnlyInput),
140 'link_id': forms.CharField(widget=forms.HiddenInput)
143 new_params['edit_template'] = 'soc/student_proposal/edit.html'
144 new_params['review_template'] = 'soc/student_proposal/review.html'
146 params = dicts.merge(params, new_params)
148 super(View, self).__init__(params=params)
150 # create the special form for students
151 dynafields = [
152 {'name': 'organization',
153 'base': forms.CharField,
154 'label': 'Organization Link ID',
155 'widget': widgets.ReadOnlyInput(),
156 'required': False,
160 dynaproperties = params_helper.getDynaFields(dynafields)
162 student_create_form = dynaform.extendDynaForm(
163 dynaform=self._params['create_form'],
164 dynaproperties=dynaproperties)
166 params['student_create_form'] = student_create_form
168 # create the special form for public review
169 dynafields = [
170 {'name': 'comment',
171 'base': forms.CharField,
172 'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
173 'label': 'Comment',
174 'required': False,
178 dynaproperties = params_helper.getDynaFields(dynafields)
179 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
181 public_review_form = dynaform.newDynaForm(dynamodel=None,
182 dynabase=helper.forms.BaseForm, dynainclude=None,
183 dynaexclude=None, dynaproperties=dynaproperties)
184 params['public_review_form'] = public_review_form
186 # create the special form for mentors
187 dynafields = [
188 {'name': 'score',
189 'base': forms.ChoiceField,
190 'label': 'Score',
191 'initial': 0,
192 'required': False,
193 'passthrough': ['initial', 'required', 'choices'],
194 'example_text':
195 'A score will only be assigned if the review is private!',
196 'choices': [(-4,'-4: Wow. This. Sucks.'),
197 (-3,'-3: Needs a lot of work'),
198 (-2,'-2: This is bad'),
199 (-1,'-1: I dont like this'),
200 (0,'0: No score'),
201 (1,'1: Might have potential'),
202 (2,'2: Good'),
203 (3,'3: Almost there'),
204 (4,'4: Made. Of. Awesome.')]
206 {'name': 'comment',
207 'base': forms.CharField,
208 'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
209 'label': 'Comment',
210 'required': False,
212 {'name': 'public',
213 'base': forms.BooleanField,
214 'label': 'Public review',
215 'initial': False,
216 'required': False,
217 'help_text': 'By ticking this box the score will not be assigned, '
218 'and the review will be public.',
222 dynaproperties = params_helper.getDynaFields(dynafields)
223 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
225 mentor_review_form = dynaform.newDynaForm(dynamodel=None,
226 dynabase=helper.forms.BaseForm, dynainclude=None,
227 dynaexclude=None, dynaproperties=dynaproperties)
228 params['mentor_review_form'] = mentor_review_form
230 # TODO see if autocomplete can be used for mentor field
231 dynafields = [
232 {'name': 'rank',
233 'base': forms.IntegerField,
234 'label': 'Set to rank',
235 'help_text':
236 'Set this proposal to the given rank (ignores the given score)',
237 'example_text': 'A rank will only be assigned if the '
238 'review is private!',
239 'min_value': 1,
240 'required': False,
241 'passthrough': ['min_value', 'required', 'help_text'],
243 {'name': 'mentor',
244 'base': forms.CharField,
245 'label': 'Assign Mentor (Link ID)',
246 'required': False,
247 'help_text': 'Fill in the Link ID of the Mentor '
248 'you would like to assign to this Proposal. '
249 'Leave this box empty if you don\'t want any mentor assigned.',
253 dynaproperties = params_helper.getDynaFields(dynafields)
254 dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')
256 admin_review_form = dynaform.extendDynaForm(dynaform=mentor_review_form,
257 dynaproperties=dynaproperties)
259 params['admin_review_form'] = admin_review_form
261 def _editGet(self, request, entity, form):
262 """See base.View._editGet().
265 form.fields['link_id'].initial = entity.link_id
266 form.fields['organization'].initial = entity.org.link_id
268 return super(View, self)._editGet(request, entity, form)
270 def _editPost(self, request, entity, fields):
271 """See base.View._editPost().
274 if not entity:
275 fields['link_id'] = 't%i' % (int(time.time()*100))
276 else:
277 fields['link_id'] = entity.link_id
279 # fill in the scope via call to super
280 super(View, self)._editPost(request, entity, fields)
282 if not entity:
283 # creating a new application so set the program and org field
284 fields['program'] = fields['scope'].scope
286 filter = {'scope': fields['program'],
287 'link_id': fields['organization']}
288 fields['org'] = org_logic.logic.getForFields(filter, unique=True)
290 # explicitly change the last_modified_on since the content has been edited
291 fields['last_modified_on'] = datetime.datetime.now()
293 @decorators.merge_params
294 @decorators.check_access
295 def public(self, request, access_type,
296 page_name=None, params=None, **kwargs):
297 """View in which the student can see and reply to the comments on the
298 Student Proposal.
300 For params see base.view.Public().
303 context = helper.responses.getUniversalContext(request)
304 helper.responses.useJavaScript(context, params['js_uses_all'])
305 context['page_name'] = page_name
307 try:
308 entity = self._logic.getFromKeyFieldsOr404(kwargs)
309 except out_of_band.Error, error:
310 return helper.responses.errorResponse(
311 error, request, template=params['error_public'], context=context)
313 context['entity'] = entity
314 context['entity_type'] = params['name']
315 context['entity_type_url'] = params['url_name']
317 if request.method == 'POST':
318 return self.publicPost(request, context, params, entity, **kwargs)
319 else: # request.method == 'GET'
320 return self.publicGet(request, context, params, entity, **kwargs)
322 def publicPost(self, request, context, params, entity, **kwargs):
323 """Handles the POST request for the entity's public page.
325 Args:
326 entity: the student proposal entity
327 rest: see base.View.public()
330 # populate the form using the POST data
331 form = params['public_review_form'](request.POST)
333 if not form.is_valid():
334 # get some entity specific context
335 self.updatePublicContext(context, entity, params)
337 # return the invalid form response
338 return self._constructResponse(request, entity=entity, context=context,
339 form=form, params=params, template=params['public_template'])
341 # get the commentary
342 fields = form.cleaned_data
343 comment = fields['comment']
345 if comment:
346 # create a new public review containing the comment
347 user_entity = user_logic.logic.getForCurrentAccount()
349 if user_entity.key() == entity.scope.user.key():
350 # student is posting
351 reviewer = entity.scope
352 else:
353 # check if the person commenting is an org_admin
354 # or a mentor for the given proposal
355 fields = {'user': user_entity,
356 'scope': entity.org,
357 'status': 'active',
360 reviewer = org_admin_logic.logic.getForFields(fields, unique=True)
362 if not reviewer:
363 # no org_admin found, maybe it's a mentor?
364 reviewer = mentor_logic.logic.getForFields(fields, unique=True)
366 # create the review (reviewer might be None
367 # if a Host or Developer is posting)
368 self._createReviewFor(entity, reviewer, comment, is_public=True)
370 # redirect to the same page
371 return http.HttpResponseRedirect('')
373 def publicGet(self, request, context, params, entity, **kwargs):
374 """Handles the GET request for the entity's public page.
376 Args:
377 entity: the student proposal entity
378 rest see base.View.public()
381 from soc.logic.models.review_follower import logic as review_follower_logic
383 get_dict = request.GET
385 if get_dict.get('subscription') and (
386 get_dict['subscription'] in ['on', 'off']):
388 subscription = get_dict['subscription']
390 # get the current user
391 user_entity = user_logic.logic.getForCurrentAccount()
393 # create the fields that should be in the ReviewFollower entity
394 fields = {'link_id': user_entity.link_id,
395 'scope': entity,
396 'scope_path': entity.key().name(),
397 'user': user_entity
399 # get the keyname for the ReviewFollower entity
400 key_name = review_follower_logic.getKeyNameFromFields(fields)
402 # determine if we should set subscribed_public to True or False
403 if subscription == 'on':
404 fields['subscribed_public'] = True
405 elif subscription == 'off':
406 fields['subscribed_public'] = False
408 # update the ReviewFollower
409 review_follower_logic.updateOrCreateFromKeyName(fields, key_name)
411 # get some entity specific context
412 self.updatePublicContext(context, entity, params)
414 context['form'] = params['public_review_form']()
415 template = params['public_template']
417 return responses.respond(request, template, context=context)
419 def updatePublicContext(self, context, entity, params):
420 """Updates the context for the public page with information from the entity.
422 Args:
423 context: the context that should be updated
424 entity: a student proposal_entity used to set context
425 params: dict with params for the view using this context
428 from soc.logic.models.review import logic as review_logic
429 from soc.logic.models.review_follower import logic as review_follower_logic
431 student_entity = entity.scope
433 context['student'] = student_entity
434 context['student_name'] = student_entity.name()
436 user_entity = user_logic.logic.getForCurrentAccount()
438 # check if the current user is the student
439 if user_entity.key() == student_entity.user.key():
440 # show the proposal edit link
441 context['edit_link'] = redirects.getEditRedirect(entity, params)
443 # check if the current user is subscribed to this proposal's public reviews
444 fields = {'user': user_entity,
445 'scope': entity,
446 'subscribed_public': True}
448 context['is_subscribed'] = review_follower_logic.getForFields(fields,
449 unique=True)
451 context['public_reviews'] = review_logic.getReviewsForEntity(entity,
452 is_public=True, order=['created'])
454 @decorators.merge_params
455 @decorators.check_access
456 def apply(self, request, access_type,
457 page_name=None, params=None, **kwargs):
458 """Special view used to prepopulate the form with the organization
459 contributors template.
461 For params see base.View.public()
463 get_dict = request.GET
465 if get_dict.get('organization'):
466 # organization chosen, prepopulate with template
468 # get the organization
469 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
470 program_entity = student_entity.scope
472 filter = {'link_id': get_dict['organization'],
473 'scope': program_entity}
475 org_entity = org_logic.logic.getForFields(filter, unique=True)
477 if org_entity:
478 # organization found use special form
479 params['create_form'] = params['student_create_form']
480 kwargs['content'] = org_entity.contrib_template
482 # Create page is an edit page with no key fields
483 empty_kwargs = {}
484 fields = self._logic.getKeyFieldNames()
485 for field in fields:
486 empty_kwargs[field] = None
488 return super(View, self).edit(request, access_type, page_name=page_name,
489 params=params, seed=kwargs, **empty_kwargs)
491 @decorators.merge_params
492 @decorators.check_access
493 def edit(self, request, access_type,
494 page_name=None, params=None, seed=None, **kwargs):
495 """If the POST contains (action, Withdraw) the proposal in kwargs
496 will be marked as invalid.
498 For params see base.View.edit()
501 # check if request.POST contains action
502 post_dict = request.POST
503 if 'action' in post_dict and post_dict['action'] == 'Withdraw':
504 # withdraw this proposal
505 filter = {'scope_path': kwargs['scope_path'],
506 'link_id': kwargs['link_id']}
508 proposal_logic = params['logic']
509 student_proposal_entity = proposal_logic.getForFields(filter, unique=True)
511 # update the entity mark it as invalid
512 proposal_logic.updateEntityProperties(student_proposal_entity,
513 {'status': 'invalid'})
515 # redirect to the program's homepage
516 redirect_url = redirects.getHomeRedirect(student_proposal_entity.program,
517 {'url_name': 'program'})
519 return http.HttpResponseRedirect(redirect_url)
521 return super(View, self).edit(request=request, access_type=access_type,
522 page_name=page_name, params=params, seed=seed, **kwargs)
524 @decorators.merge_params
525 @decorators.check_access
526 def listOrgs(self, request, access_type,
527 page_name=None, params=None, **kwargs):
528 """Lists all organization which the given student can propose to.
530 For params see base.View.public().
533 from soc.views.models import organization as org_view
535 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
537 filter = {'scope': student_entity.scope,
538 'status': 'active'}
540 list_params = org_view.view.getParams().copy()
541 list_params['list_description'] = ('List of %(name_plural)s you can send '
542 'your proposal to.') % list_params
543 list_params['list_action'] = (redirects.getStudentProposalRedirect,
544 {'student_key': student_entity.key().name(),
545 'url_name': params['url_name']})
547 return self.list(request, access_type=access_type, page_name=page_name,
548 params=list_params, filter=filter, **kwargs)
550 @decorators.merge_params
551 @decorators.check_access
552 def listSelf(self, request, access_type,
553 page_name=None, params=None, **kwargs):
554 """Lists all proposals from the current logged-in user
555 for the given student.
557 For params see base.View.public().
560 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
562 filter = {'scope' : student_entity,
563 'status': ['new', 'pending', 'accepted', 'rejected']}
565 list_params = params.copy()
566 list_params['list_description'] = 'List of my %(name_plural)s' % list_params
567 list_params['list_action'] = (redirects.getPublicRedirect, list_params)
569 return self.list(request, access_type=access_type, page_name=page_name,
570 params=list_params, filter=filter, **kwargs)
572 @decorators.merge_params
573 @decorators.check_access
574 def review(self, request, access_type,
575 page_name=None, params=None, **kwargs):
576 """View that allows Organization Admins and Mentors to review the proposal.
578 For Args see base.View.public().
581 try:
582 entity = self._logic.getFromKeyFieldsOr404(kwargs)
583 except out_of_band.Error, error:
584 return helper.responses.errorResponse(
585 error, request, template=params['error_public'])
587 # get the context for this webpage
588 context = responses.getUniversalContext(request)
589 responses.useJavaScript(context, params['js_uses_all'])
590 context['page_name'] = '%s "%s" from %s' % (page_name, entity.title,
591 entity.scope.name())
592 context['entity'] = entity
593 context['entity_type'] = params['name']
594 context['entity_type_url'] = params['url_name']
596 # get the roles important for reviewing an application
597 filter = {'user': user_logic.logic.getForCurrentAccount(),
598 'scope': entity.org,
599 'status': 'active'}
601 org_admin_entity = org_admin_logic.logic.getForFields(filter, unique=True)
602 mentor_entity = mentor_logic.logic.getForFields(filter, unique=True)
604 # decide which form to use
605 if org_admin_entity:
606 form = params['admin_review_form']
607 else:
608 form = params['mentor_review_form']
610 if request.method == 'POST':
611 return self.reviewPost(request, context, params, entity,
612 form, org_admin_entity, mentor_entity, **kwargs)
613 else:
614 # request.method == 'GET'
615 return self.reviewGet(request, context, params, entity,
616 form, org_admin_entity, mentor_entity, **kwargs)
618 def reviewPost(self, request, context, params, entity, form,
619 org_admin, mentor, **kwargs):
620 """Handles the POST request for the proposal review view.
622 Args:
623 entity: the student proposal entity
624 form: the form to use in this view
625 org_admin: org admin entity for the current user/proposal (iff available)
626 mentor: mentor entity for the current user/proposal (iff available)
627 rest: see base.View.public()
629 # populate the form using the POST data
630 form = form(request.POST)
632 if not form.is_valid():
633 # return the invalid form response
634 # get all the extra information that should be in the context
635 review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
636 context = dicts.merge(context, review_context)
638 return self._constructResponse(request, entity=entity, context=context,
639 form=form, params=params, template=params['review_template'])
641 fields = form.cleaned_data
642 is_public = fields['public']
643 comment = fields['comment']
644 given_score = int(fields['score'])
646 if org_admin:
647 # org admin found, try to adjust the assigned mentor
648 self._adjustMentor(entity, fields['mentor'])
649 reviewer = org_admin
651 # try to see if the rank is given and adjust the given_score if needed
652 rank = fields['rank']
653 if rank:
654 ranker = self._logic.getRankerFor(entity)
655 # if a very high rank is filled in use the highest
656 # one that returns a score
657 rank = min(ranker.TotalRankedScores(), rank)
658 # ranker uses zero-based ranking
659 score_and_rank = ranker.FindScore(rank-1)
660 # get the score at the requested rank
661 score_at_rank = score_and_rank[0][0]
662 # calculate the score that should be given to end up at the given rank
663 given_score = score_at_rank - entity.score
664 else:
665 # might be None (if Host or Developer is commenting)
666 reviewer = mentor
668 # store the properties to update the proposal with
669 properties = {}
671 if reviewer and (not is_public) and (given_score is not 0):
672 # if it is not a public comment and it's made by a member of the
673 # organization we update the score of the proposal
674 new_score = given_score + entity.score
675 properties = {'score': new_score}
677 if comment or (given_score is not 0):
678 # if the proposal is new we change it status to pending
679 if entity.status == 'new':
680 properties['status'] = 'pending'
682 # create the review entity
683 self._createReviewFor(entity, reviewer, comment, given_score, is_public)
685 if properties.values():
686 # there is something to update
687 self._logic.updateEntityProperties(entity, properties)
689 # redirect to the same page
690 return http.HttpResponseRedirect('')
692 def reviewGet(self, request, context, params, entity, form,
693 org_admin, mentor, **kwargs):
694 """Handles the GET request for the proposal review view.
696 Args:
697 entity: the student proposal entity
698 form: the form to use in this view
699 org_admin: org admin entity for the current user/proposal (iff available)
700 mentor: mentor entity for the current user/proposal (iff available)
701 rest: see base.View.public()
704 from soc.logic.models.review_follower import logic as review_follower_logic
706 get_dict = request.GET
708 # check if the current user is a mentor and wants
709 # to change his role for this app
710 choice = get_dict.get('mentor')
711 if mentor and choice:
712 self._adjustPossibleMentors(entity, mentor, choice)
714 ineligible = get_dict.get('ineligible')
716 if org_admin:
717 reviewer = org_admin
718 elif mentor:
719 reviewer = mentor
721 if (org_admin or mentor) and ineligible != None:
722 ineligible = int(ineligible)
723 if ineligible == 1:
724 # mark the proposal invalid and return to the list
725 properties = {'status': 'invalid'}
726 self._logic.updateEntityProperties(entity, properties)
728 redirect = redirects.getListProposalsRedirect(entity.org,
729 {'url_name': 'org'})
730 comment = "Marked Student Proposal as Ineligible."
731 self._createReviewFor(entity, reviewer, comment, is_public=False)
732 return http.HttpResponseRedirect(redirect)
733 elif ineligible == 0:
734 # mark the proposal as new and return to the list
735 properties = {'status': 'new'}
736 self._logic.updateEntityProperties(entity, properties)
738 redirect = redirects.getListProposalsRedirect(entity.org,
739 {'url_name': 'org'})
740 comment = "Marked Student Proposal as Eligible."
741 self._createReviewFor(entity, reviewer, comment, is_public=False)
742 return http.HttpResponseRedirect(redirect)
744 # check if we should change the subscription state for the current user
745 public_subscription = None
746 private_subscription = None
748 if get_dict.get('public_subscription') and (
749 get_dict['public_subscription'] in ['on', 'off']):
751 public_subscription = get_dict['public_subscription'] == 'on'
753 if get_dict.get('private_subscription') and (
754 get_dict['private_subscription'] in ['on', 'off']):
755 private_subscription = get_dict['private_subscription'] == 'on'
757 if public_subscription != None or private_subscription != None:
758 # get the current user
759 user_entity = user_logic.logic.getForCurrentAccount()
761 # create the fields that should be in the ReviewFollower entity
762 fields = {'link_id': user_entity.link_id,
763 'scope': entity,
764 'scope_path': entity.key().name(),
765 'user': user_entity
767 # get the keyname for the ReviewFollower entity
768 key_name = review_follower_logic.getKeyNameFromFields(fields)
770 # determine which subscription properties we should change
771 if public_subscription != None:
772 fields['subscribed_public'] = public_subscription
774 if private_subscription != None:
775 fields['subscribed_private'] = private_subscription
777 # update the ReviewFollower
778 review_follower_logic.updateOrCreateFromKeyName(fields, key_name)
780 # set the initial score since the default is ignored
781 initial = {'score': 0}
783 if org_admin and entity.mentor:
784 # set the mentor field to the current mentor
785 initial['mentor'] = entity.mentor.link_id
787 context['form'] = form(initial)
789 # create the special form for mentors
790 comment_public = ['public', 'comment']
791 comment_private = ['score']
792 comment_admin = ['rank', 'mentor']
793 class FilterForm(object):
794 """Helper class used for form filtering.
796 def __init__(self, form, fields):
797 self.__form = form
798 self.__fields = fields
800 @property
801 def fields(self):
802 """Property that returns all fields as dictionary."""
803 fields = self.__form.fields.iteritems()
804 return dict([(k, i) for k, i in fields if k in self.__fields])
806 def __iter__(self):
807 for field in self.__form:
808 if field.name not in self.__fields:
809 continue
810 yield field
812 _marker = []
813 def __getattr__(self, key, default=_marker):
814 if default is self._marker:
815 return getattr(self.__form, key)
816 else:
817 return getattr(self.__form, key, default)
819 context['form'] = form(initial)
820 context['comment_public'] = FilterForm(context['form'], comment_public)
821 context['comment_private'] = FilterForm(context['form'], comment_private)
822 context['comment_admin'] = FilterForm(context['form'], comment_admin)
824 # get all the extra information that should be in the context
825 review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
826 context = dicts.merge(context, review_context)
828 template = params['review_template']
830 return responses.respond(request, template, context=context)
832 def _getDefaultReviewContext(self, entity, org_admin,
833 mentor):
834 """Returns the default context for the review page.
836 Args:
837 entity: Student Proposal entity
838 org_admin: org admin entity for the current user/proposal (iff available)
839 mentor: mentor entity for the current user/proposal (iff available)
842 from soc.logic.models.review import logic as review_logic
843 from soc.logic.models.review_follower import logic as review_follower_logic
845 context = {}
847 context['student'] = entity.scope
848 context['student_name'] = entity.scope.name()
850 if entity.mentor:
851 context['mentor_name'] = entity.mentor.name()
852 else:
853 context['mentor_name'] = "No mentor assigned"
855 # set the possible mentors in the context
856 possible_mentors = entity.possible_mentors
858 if not possible_mentors:
859 context['possible_mentors'] = "None"
860 else:
861 mentor_names = []
863 for mentor_key in possible_mentors:
864 possible_mentor = mentor_logic.logic.getFromKeyName(mentor_key.name())
865 mentor_names.append(possible_mentor.name())
867 context['possible_mentors'] = ', '.join(mentor_names)
869 # TODO(ljvderijk) listing of total given scores per mentor
870 # a dict with key as role.user ?
872 # order the reviews by ascending creation date
873 order = ['created']
875 # get the public reviews
876 context['public_reviews'] = review_logic.getReviewsForEntity(entity,
877 is_public=True, order=order)
879 # get the private reviews
880 context['private_reviews'] = review_logic.getReviewsForEntity(entity,
881 is_public=False, order=order)
883 # which button should we show to the mentor?
884 if mentor:
885 context['is_mentor'] = True
886 if mentor.key() in possible_mentors:
887 # show "No longer willing to mentor"
888 context['remove_me_as_mentor'] = True
889 else:
890 # show "I am willing to mentor"
891 context['add_me_as_mentor'] = True
893 if org_admin:
894 context['is_org_admin'] = True
896 user_entity = user_logic.logic.getForCurrentAccount()
898 # check if the current user is subscribed to public or private reviews
899 fields = {'scope': entity,
900 'user': user_entity,}
901 follower_entity = review_follower_logic.getForFields(fields, unique=True)
903 if follower_entity:
904 context['is_subscribed_public'] = follower_entity.subscribed_public
905 context['is_subscribed_private'] = follower_entity.subscribed_private
907 return context
909 def _adjustPossibleMentors(self, entity, mentor, choice):
910 """Adjusts the possible mentors list for a proposal.
912 Args:
913 entity: Student Proposal entity
914 mentor: Mentor entity
915 choice: 1 means want to mentor, 0 do not want to mentor
917 possible_mentors = entity.possible_mentors
919 if choice == '1':
920 # add the mentor to possible mentors list if not already in
921 if mentor.key() not in possible_mentors:
922 possible_mentors.append(mentor.key())
923 fields = {'possible_mentors': possible_mentors}
924 self._logic.updateEntityProperties(entity, fields)
925 elif choice == '0':
926 # remove the mentor from the possible mentors list
927 if mentor.key() in possible_mentors:
928 possible_mentors.remove(mentor.key())
929 fields = {'possible_mentors': possible_mentors}
930 self._logic.updateEntityProperties(entity, fields)
932 def _adjustMentor(self, entity, mentor_id):
933 """Changes the mentor to the given link_id.
935 Args:
936 entity: Student Proposal entity
937 mentor_id: Link ID of the mentor that needs to be assigned
938 Iff not given then removes the assigned mentor
941 if entity.mentor and entity.mentor.link_id == mentor_id:
942 # no need to change
943 return
945 if mentor_id:
946 # try to locate the mentor
947 fields = {'link_id': mentor_id,
948 'scope': entity.org,
949 'status': 'active'}
951 mentor_entity = mentor_logic.logic.getForFields(fields, unique=True)
953 if not mentor_entity:
954 # no mentor found, do not update
955 return
956 else:
957 # reset to None
958 mentor_entity = None
960 # update the proposal
961 properties = {'mentor': mentor_entity}
962 self._logic.updateEntityProperties(entity, properties)
964 def _createReviewFor(self, entity, reviewer, comment,
965 score=0, is_public=True):
966 """Creates a review for the given proposal and sends
967 out a message to all followers.
969 Args:
970 entity: Student Proposal entity for which the review should be created
971 reviewer: A role entity of the reviewer (if possible, else None)
972 comment: The textual contents of the review
973 score: The score of the review (only used if the review is not public)
974 is_public: Determines if the review is a public review
977 from soc.logic.helper import notifications as notifications_helper
978 from soc.logic.models.review import logic as review_logic
979 from soc.logic.models.review_follower import logic as review_follower_logic
981 # create the fields for the review entity
982 fields = {'link_id': 't%i' % (int(time.time()*100)),
983 'scope': entity,
984 'scope_path': entity.key().name(),
985 'author': user_logic.logic.getForCurrentAccount(),
986 'content': comment,
987 'is_public': is_public,
988 'reviewer': reviewer
991 # add the given score if the review is not public
992 if not is_public:
993 fields['score'] = score
995 # create a new Review
996 key_name = review_logic.getKeyNameFromFields(fields)
997 review_entity = review_logic.updateOrCreateFromKeyName(fields, key_name)
999 # get all followers
1000 fields = {'scope': entity}
1002 if is_public:
1003 fields['subscribed_public'] = True
1004 else:
1005 fields['subscribed_private'] = True
1007 followers = review_follower_logic.getForFields(fields)
1009 if is_public:
1010 # redirect to public page
1011 redirect_url = redirects.getPublicRedirect(entity, self._params)
1012 else:
1013 # redirect to review page
1014 redirect_url = redirects.getReviewRedirect(entity, self._params)
1016 for follower in followers:
1017 # sent to every follower except the reviewer
1018 if follower.user.key() != review_entity.author.key():
1019 notifications_helper.sendNewReviewNotification(follower.user,
1020 review_entity, entity.title, redirect_url)
1023 view = View()
1025 admin = decorators.view(view.admin)
1026 apply = decorators.view(view.apply)
1027 create = decorators.view(view.create)
1028 delete = decorators.view(view.delete)
1029 edit = decorators.view(view.edit)
1030 list = decorators.view(view.list)
1031 list_orgs = decorators.view(view.listOrgs)
1032 list_self = decorators.view(view.listSelf)
1033 public = decorators.view(view.public)
1034 review = decorators.view(view.review)
1035 export = decorators.view(view.export)
1036 pick = decorators.view(view.pick)