When Student withdrew a proposal a public comment "Student withdrew proposal." is...
[Melange.git] / app / soc / views / models / student_proposal.py
blobc6750489a4c419799d136a051a369c3a5700cd84
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)
510 reviewer = student_proposal_entity.scope
512 # update the entity mark it as invalid
513 proposal_logic.updateEntityProperties(student_proposal_entity,
514 {'status': 'invalid'})
516 # redirect to the program's homepage
517 redirect_url = redirects.getHomeRedirect(student_proposal_entity.program,
518 {'url_name': 'program'})
520 comment = "Student withdrew proposal."
521 self._createReviewFor(student_proposal_entity, reviewer, comment)
522 return http.HttpResponseRedirect(redirect_url)
524 return super(View, self).edit(request=request, access_type=access_type,
525 page_name=page_name, params=params, seed=seed, **kwargs)
527 @decorators.merge_params
528 @decorators.check_access
529 def listOrgs(self, request, access_type,
530 page_name=None, params=None, **kwargs):
531 """Lists all organization which the given student can propose to.
533 For params see base.View.public().
536 from soc.views.models import organization as org_view
538 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
540 filter = {'scope': student_entity.scope,
541 'status': 'active'}
543 list_params = org_view.view.getParams().copy()
544 list_params['list_description'] = ('List of %(name_plural)s you can send '
545 'your proposal to.') % list_params
546 list_params['list_action'] = (redirects.getStudentProposalRedirect,
547 {'student_key': student_entity.key().name(),
548 'url_name': params['url_name']})
550 return self.list(request, access_type=access_type, page_name=page_name,
551 params=list_params, filter=filter, **kwargs)
553 @decorators.merge_params
554 @decorators.check_access
555 def listSelf(self, request, access_type,
556 page_name=None, params=None, **kwargs):
557 """Lists all proposals from the current logged-in user
558 for the given student.
560 For params see base.View.public().
563 student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
565 filter = {'scope' : student_entity,
566 'status': ['new', 'pending', 'accepted', 'rejected']}
568 list_params = params.copy()
569 list_params['list_description'] = 'List of my %(name_plural)s' % list_params
570 list_params['list_action'] = (redirects.getPublicRedirect, list_params)
572 return self.list(request, access_type=access_type, page_name=page_name,
573 params=list_params, filter=filter, **kwargs)
575 @decorators.merge_params
576 @decorators.check_access
577 def review(self, request, access_type,
578 page_name=None, params=None, **kwargs):
579 """View that allows Organization Admins and Mentors to review the proposal.
581 For Args see base.View.public().
584 try:
585 entity = self._logic.getFromKeyFieldsOr404(kwargs)
586 except out_of_band.Error, error:
587 return helper.responses.errorResponse(
588 error, request, template=params['error_public'])
590 # get the context for this webpage
591 context = responses.getUniversalContext(request)
592 responses.useJavaScript(context, params['js_uses_all'])
593 context['page_name'] = '%s "%s" from %s' % (page_name, entity.title,
594 entity.scope.name())
595 context['entity'] = entity
596 context['entity_type'] = params['name']
597 context['entity_type_url'] = params['url_name']
599 # get the roles important for reviewing an application
600 filter = {'user': user_logic.logic.getForCurrentAccount(),
601 'scope': entity.org,
602 'status': 'active'}
604 org_admin_entity = org_admin_logic.logic.getForFields(filter, unique=True)
605 mentor_entity = mentor_logic.logic.getForFields(filter, unique=True)
607 # decide which form to use
608 if org_admin_entity:
609 form = params['admin_review_form']
610 else:
611 form = params['mentor_review_form']
613 if request.method == 'POST':
614 return self.reviewPost(request, context, params, entity,
615 form, org_admin_entity, mentor_entity, **kwargs)
616 else:
617 # request.method == 'GET'
618 return self.reviewGet(request, context, params, entity,
619 form, org_admin_entity, mentor_entity, **kwargs)
621 def reviewPost(self, request, context, params, entity, form,
622 org_admin, mentor, **kwargs):
623 """Handles the POST request for the proposal review view.
625 Args:
626 entity: the student proposal entity
627 form: the form to use in this view
628 org_admin: org admin entity for the current user/proposal (iff available)
629 mentor: mentor entity for the current user/proposal (iff available)
630 rest: see base.View.public()
632 # populate the form using the POST data
633 form = form(request.POST)
635 if not form.is_valid():
636 # return the invalid form response
637 # get all the extra information that should be in the context
638 review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
639 context = dicts.merge(context, review_context)
641 return self._constructResponse(request, entity=entity, context=context,
642 form=form, params=params, template=params['review_template'])
644 fields = form.cleaned_data
645 is_public = fields['public']
646 comment = fields['comment']
647 given_score = int(fields['score'])
649 if org_admin:
650 # org admin found, try to adjust the assigned mentor
651 self._adjustMentor(entity, fields['mentor'])
652 reviewer = org_admin
654 # try to see if the rank is given and adjust the given_score if needed
655 rank = fields['rank']
656 if rank:
657 ranker = self._logic.getRankerFor(entity)
658 # if a very high rank is filled in use the highest
659 # one that returns a score
660 rank = min(ranker.TotalRankedScores(), rank)
661 # ranker uses zero-based ranking
662 score_and_rank = ranker.FindScore(rank-1)
663 # get the score at the requested rank
664 score_at_rank = score_and_rank[0][0]
665 # calculate the score that should be given to end up at the given rank
666 given_score = score_at_rank - entity.score
667 else:
668 # might be None (if Host or Developer is commenting)
669 reviewer = mentor
671 # store the properties to update the proposal with
672 properties = {}
674 if reviewer and (not is_public) and (given_score is not 0):
675 # if it is not a public comment and it's made by a member of the
676 # organization we update the score of the proposal
677 new_score = given_score + entity.score
678 properties = {'score': new_score}
680 if comment or (given_score is not 0):
681 # if the proposal is new we change it status to pending
682 if entity.status == 'new':
683 properties['status'] = 'pending'
685 # create the review entity
686 self._createReviewFor(entity, reviewer, comment, given_score, is_public)
688 if properties.values():
689 # there is something to update
690 self._logic.updateEntityProperties(entity, properties)
692 # redirect to the same page
693 return http.HttpResponseRedirect('')
695 def reviewGet(self, request, context, params, entity, form,
696 org_admin, mentor, **kwargs):
697 """Handles the GET request for the proposal review view.
699 Args:
700 entity: the student proposal entity
701 form: the form to use in this view
702 org_admin: org admin entity for the current user/proposal (iff available)
703 mentor: mentor entity for the current user/proposal (iff available)
704 rest: see base.View.public()
707 from soc.logic.models.review_follower import logic as review_follower_logic
709 get_dict = request.GET
711 # check if the current user is a mentor and wants
712 # to change his role for this app
713 choice = get_dict.get('mentor')
714 if mentor and choice:
715 self._adjustPossibleMentors(entity, mentor, choice)
717 ineligible = get_dict.get('ineligible')
719 if org_admin:
720 reviewer = org_admin
721 elif mentor:
722 reviewer = mentor
724 if (org_admin or mentor) and ineligible != None:
725 ineligible = int(ineligible)
726 if ineligible == 1:
727 # mark the proposal invalid and return to the list
728 properties = {'status': 'invalid'}
729 self._logic.updateEntityProperties(entity, properties)
731 redirect = redirects.getListProposalsRedirect(entity.org,
732 {'url_name': 'org'})
733 comment = "Marked Student Proposal as Ineligible."
734 self._createReviewFor(entity, reviewer, comment, is_public=False)
735 return http.HttpResponseRedirect(redirect)
736 elif ineligible == 0:
737 # mark the proposal as new and return to the list
738 properties = {'status': 'new'}
739 self._logic.updateEntityProperties(entity, properties)
741 redirect = redirects.getListProposalsRedirect(entity.org,
742 {'url_name': 'org'})
743 comment = "Marked Student Proposal as Eligible."
744 self._createReviewFor(entity, reviewer, comment, is_public=False)
745 return http.HttpResponseRedirect(redirect)
747 # check if we should change the subscription state for the current user
748 public_subscription = None
749 private_subscription = None
751 if get_dict.get('public_subscription') and (
752 get_dict['public_subscription'] in ['on', 'off']):
754 public_subscription = get_dict['public_subscription'] == 'on'
756 if get_dict.get('private_subscription') and (
757 get_dict['private_subscription'] in ['on', 'off']):
758 private_subscription = get_dict['private_subscription'] == 'on'
760 if public_subscription != None or private_subscription != None:
761 # get the current user
762 user_entity = user_logic.logic.getForCurrentAccount()
764 # create the fields that should be in the ReviewFollower entity
765 fields = {'link_id': user_entity.link_id,
766 'scope': entity,
767 'scope_path': entity.key().name(),
768 'user': user_entity
770 # get the keyname for the ReviewFollower entity
771 key_name = review_follower_logic.getKeyNameFromFields(fields)
773 # determine which subscription properties we should change
774 if public_subscription != None:
775 fields['subscribed_public'] = public_subscription
777 if private_subscription != None:
778 fields['subscribed_private'] = private_subscription
780 # update the ReviewFollower
781 review_follower_logic.updateOrCreateFromKeyName(fields, key_name)
783 # set the initial score since the default is ignored
784 initial = {'score': 0}
786 if org_admin and entity.mentor:
787 # set the mentor field to the current mentor
788 initial['mentor'] = entity.mentor.link_id
790 context['form'] = form(initial)
792 # create the special form for mentors
793 comment_public = ['public', 'comment']
794 comment_private = ['score']
795 comment_admin = ['rank', 'mentor']
796 class FilterForm(object):
797 """Helper class used for form filtering.
799 def __init__(self, form, fields):
800 self.__form = form
801 self.__fields = fields
803 @property
804 def fields(self):
805 """Property that returns all fields as dictionary."""
806 fields = self.__form.fields.iteritems()
807 return dict([(k, i) for k, i in fields if k in self.__fields])
809 def __iter__(self):
810 for field in self.__form:
811 if field.name not in self.__fields:
812 continue
813 yield field
815 _marker = []
816 def __getattr__(self, key, default=_marker):
817 if default is self._marker:
818 return getattr(self.__form, key)
819 else:
820 return getattr(self.__form, key, default)
822 context['form'] = form(initial)
823 context['comment_public'] = FilterForm(context['form'], comment_public)
824 context['comment_private'] = FilterForm(context['form'], comment_private)
825 context['comment_admin'] = FilterForm(context['form'], comment_admin)
827 # get all the extra information that should be in the context
828 review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
829 context = dicts.merge(context, review_context)
831 template = params['review_template']
833 return responses.respond(request, template, context=context)
835 def _getDefaultReviewContext(self, entity, org_admin,
836 mentor):
837 """Returns the default context for the review page.
839 Args:
840 entity: Student Proposal entity
841 org_admin: org admin entity for the current user/proposal (iff available)
842 mentor: mentor entity for the current user/proposal (iff available)
845 from soc.logic.models.review import logic as review_logic
846 from soc.logic.models.review_follower import logic as review_follower_logic
848 context = {}
850 context['student'] = entity.scope
851 context['student_name'] = entity.scope.name()
853 if entity.mentor:
854 context['mentor_name'] = entity.mentor.name()
855 else:
856 context['mentor_name'] = "No mentor assigned"
858 # set the possible mentors in the context
859 possible_mentors = entity.possible_mentors
861 if not possible_mentors:
862 context['possible_mentors'] = "None"
863 else:
864 mentor_names = []
866 for mentor_key in possible_mentors:
867 possible_mentor = mentor_logic.logic.getFromKeyName(mentor_key.name())
868 mentor_names.append(possible_mentor.name())
870 context['possible_mentors'] = ', '.join(mentor_names)
872 # TODO(ljvderijk) listing of total given scores per mentor
873 # a dict with key as role.user ?
875 # order the reviews by ascending creation date
876 order = ['created']
878 # get the public reviews
879 context['public_reviews'] = review_logic.getReviewsForEntity(entity,
880 is_public=True, order=order)
882 # get the private reviews
883 context['private_reviews'] = review_logic.getReviewsForEntity(entity,
884 is_public=False, order=order)
886 # which button should we show to the mentor?
887 if mentor:
888 context['is_mentor'] = True
889 if mentor.key() in possible_mentors:
890 # show "No longer willing to mentor"
891 context['remove_me_as_mentor'] = True
892 else:
893 # show "I am willing to mentor"
894 context['add_me_as_mentor'] = True
896 if org_admin:
897 context['is_org_admin'] = True
899 user_entity = user_logic.logic.getForCurrentAccount()
901 # check if the current user is subscribed to public or private reviews
902 fields = {'scope': entity,
903 'user': user_entity,}
904 follower_entity = review_follower_logic.getForFields(fields, unique=True)
906 if follower_entity:
907 context['is_subscribed_public'] = follower_entity.subscribed_public
908 context['is_subscribed_private'] = follower_entity.subscribed_private
910 return context
912 def _adjustPossibleMentors(self, entity, mentor, choice):
913 """Adjusts the possible mentors list for a proposal.
915 Args:
916 entity: Student Proposal entity
917 mentor: Mentor entity
918 choice: 1 means want to mentor, 0 do not want to mentor
920 possible_mentors = entity.possible_mentors
922 if choice == '1':
923 # add the mentor to possible mentors list if not already in
924 if mentor.key() not in possible_mentors:
925 possible_mentors.append(mentor.key())
926 fields = {'possible_mentors': possible_mentors}
927 self._logic.updateEntityProperties(entity, fields)
928 elif choice == '0':
929 # remove the mentor from the possible mentors list
930 if mentor.key() in possible_mentors:
931 possible_mentors.remove(mentor.key())
932 fields = {'possible_mentors': possible_mentors}
933 self._logic.updateEntityProperties(entity, fields)
935 def _adjustMentor(self, entity, mentor_id):
936 """Changes the mentor to the given link_id.
938 Args:
939 entity: Student Proposal entity
940 mentor_id: Link ID of the mentor that needs to be assigned
941 Iff not given then removes the assigned mentor
944 if entity.mentor and entity.mentor.link_id == mentor_id:
945 # no need to change
946 return
948 if mentor_id:
949 # try to locate the mentor
950 fields = {'link_id': mentor_id,
951 'scope': entity.org,
952 'status': 'active'}
954 mentor_entity = mentor_logic.logic.getForFields(fields, unique=True)
956 if not mentor_entity:
957 # no mentor found, do not update
958 return
959 else:
960 # reset to None
961 mentor_entity = None
963 # update the proposal
964 properties = {'mentor': mentor_entity}
965 self._logic.updateEntityProperties(entity, properties)
967 def _createReviewFor(self, entity, reviewer, comment,
968 score=0, is_public=True):
969 """Creates a review for the given proposal and sends
970 out a message to all followers.
972 Args:
973 entity: Student Proposal entity for which the review should be created
974 reviewer: A role entity of the reviewer (if possible, else None)
975 comment: The textual contents of the review
976 score: The score of the review (only used if the review is not public)
977 is_public: Determines if the review is a public review
980 from soc.logic.helper import notifications as notifications_helper
981 from soc.logic.models.review import logic as review_logic
982 from soc.logic.models.review_follower import logic as review_follower_logic
984 # create the fields for the review entity
985 fields = {'link_id': 't%i' % (int(time.time()*100)),
986 'scope': entity,
987 'scope_path': entity.key().name(),
988 'author': user_logic.logic.getForCurrentAccount(),
989 'content': comment,
990 'is_public': is_public,
991 'reviewer': reviewer
994 # add the given score if the review is not public
995 if not is_public:
996 fields['score'] = score
998 # create a new Review
999 key_name = review_logic.getKeyNameFromFields(fields)
1000 review_entity = review_logic.updateOrCreateFromKeyName(fields, key_name)
1002 # get all followers
1003 fields = {'scope': entity}
1005 if is_public:
1006 fields['subscribed_public'] = True
1007 else:
1008 fields['subscribed_private'] = True
1010 followers = review_follower_logic.getForFields(fields)
1012 if is_public:
1013 # redirect to public page
1014 redirect_url = redirects.getPublicRedirect(entity, self._params)
1015 else:
1016 # redirect to review page
1017 redirect_url = redirects.getReviewRedirect(entity, self._params)
1019 for follower in followers:
1020 # sent to every follower except the reviewer
1021 if follower.user.key() != review_entity.author.key():
1022 notifications_helper.sendNewReviewNotification(follower.user,
1023 review_entity, entity.title, redirect_url)
1026 view = View()
1028 admin = decorators.view(view.admin)
1029 apply = decorators.view(view.apply)
1030 create = decorators.view(view.create)
1031 delete = decorators.view(view.delete)
1032 edit = decorators.view(view.edit)
1033 list = decorators.view(view.list)
1034 list_orgs = decorators.view(view.listOrgs)
1035 list_self = decorators.view(view.listSelf)
1036 public = decorators.view(view.public)
1037 review = decorators.view(view.review)
1038 export = decorators.view(view.export)
1039 pick = decorators.view(view.pick)