app/soc/views/models/student_proposal.py
author Sverre Rabbelier <srabbelier@gmail.com>
Mon, 13 Apr 2009 15:31:39 +0000
changeset 2177 e2c193e1f631
parent 2174 19ed1c42e836
child 2179 efc3a50a81ef
permissions -rw-r--r--
Do not rely on dicts.merge to change target Also make dicts.merge actually not touch target. This is much cleaner than modifying in place, especially since we assign the result of the dicts.merge call to target most of the time anyway. Patch by: Sverre Rabbelier

#!/usr/bin/python2.5
#
# Copyright 2009 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Views for Student Proposal.
"""

__authors__ = [
    '"Lennard de Rijk" <ljvderijk@gmail.com>',
  ]


import datetime
import time

from django import forms
from django import http
from django.utils.translation import ugettext

from soc.logic import cleaning
from soc.logic import dicts
from soc.logic.models import mentor as mentor_logic
from soc.logic.models import organization as org_logic
from soc.logic.models import org_admin as org_admin_logic
from soc.logic.models import student as student_logic
from soc.logic.models import user as user_logic
from soc.views import helper
from soc.views import out_of_band
from soc.views.helper import access
from soc.views.helper import decorators
from soc.views.helper import dynaform
from soc.views.helper import lists
from soc.views.helper import params as params_helper
from soc.views.helper import redirects
from soc.views.helper import responses
from soc.views.helper import widgets
from soc.views.models import base
from soc.views.models import student as student_view

import soc.logic.models.student_proposal


class View(base.View):
  """View methods for the Student Proposal model.
  """

  def __init__(self, params=None):
    """Defines the fields and methods required for the base View class
    to provide the user with list, public, create, edit and delete views.

    Params:
      params: a dict with params for this View
    """

    rights = access.Checker(params)
    rights['create'] = ['checkIsDeveloper']
    rights['edit'] = [('checkCanStudentPropose', ['scope_path', False]),
        ('checkRoleAndStatusForStudentProposal',
            [['proposer'], ['active'], ['new', 'pending', 'invalid']])]
    rights['delete'] = ['checkIsDeveloper']
    rights['show'] = [
        ('checkRoleAndStatusForStudentProposal',
            [['proposer', 'org_admin', 'mentor', 'host'], 
            ['active', 'inactive'], 
            ['new', 'pending', 'accepted', 'rejected', 'invalid']])]
    rights['list'] = ['checkIsDeveloper']
    rights['list_orgs'] = [
        ('checkIsStudent', ['scope_path', ['active']]),
        ('checkCanStudentPropose', ['scope_path', False])]
    rights['list_self'] = [
        ('checkIsStudent', ['scope_path', ['active', 'inactive']])]
    rights['apply'] = [
        ('checkIsStudent', ['scope_path', ['active']]),
        ('checkCanStudentPropose', ['scope_path', True])]
    rights['review'] = [('checkRoleAndStatusForStudentProposal',
            [['org_admin', 'mentor', 'host'], 
            ['active'], ['new', 'pending', 'invalid']])]

    new_params = {}
    new_params['logic'] = soc.logic.models.student_proposal.logic
    new_params['rights'] = rights
    new_params['name'] = "Student Proposal"
    new_params['url_name'] = "student_proposal"
    new_params['sidebar_grouping'] = 'Students'

    new_params['scope_view'] = student_view
    new_params['scope_redirect'] = redirects.getCreateRedirect

    new_params['no_create_with_key_fields'] = True

    patterns = [
        (r'^%(url_name)s/(?P<access_type>apply)/%(scope)s$',
        'soc.views.models.%(module_name)s.apply',
        'Create a new %(name)s'),
        (r'^%(url_name)s/(?P<access_type>list_self)/%(scope)s$',
        'soc.views.models.%(module_name)s.list_self',
        'List my %(name_plural)s'),
        (r'^%(url_name)s/(?P<access_type>list_orgs)/%(scope)s$',
        'soc.views.models.%(module_name)s.list_orgs',
        'List my %(name_plural)s'),
        (r'^%(url_name)s/(?P<access_type>review)/%(key_fields)s$',
        'soc.views.models.%(module_name)s.review',
        'Review %(name)s'),
    ]

    new_params['extra_django_patterns'] = patterns

    new_params['extra_dynaexclude'] = ['org', 'program', 'score',
                                       'status', 'mentor', 'link_id',
                                       'possible_mentors']

    new_params['create_extra_dynaproperties'] = {
        'content': forms.fields.CharField(required=True,
            widget=widgets.FullTinyMCE(attrs={'rows': 25, 'cols': 100})),
        'scope_path': forms.CharField(widget=forms.HiddenInput,
            required=True),
        'organization': forms.CharField(label='Organization Link ID',
            required=True),
        'clean_abstract': cleaning.clean_content_length('abstract'),
        'clean_content': cleaning.clean_html_content('content'),
        'clean_organization': cleaning.clean_link_id('organization'),
        'clean_additional_info': cleaning.clean_url('additional_info'),
        'clean': cleaning.validate_student_proposal('organization',
            'scope_path', student_logic, org_logic),
        }

    new_params['edit_extra_dynaproperties'] = {
        'organization': forms.CharField(label='Organization Link ID',
            widget=widgets.ReadOnlyInput),
        'link_id': forms.CharField(widget=forms.HiddenInput)
        }

    new_params['edit_template'] = 'soc/student_proposal/edit.html'
    new_params['review_template'] = 'soc/student_proposal/review.html'

    params = dicts.merge(params, new_params)

    super(View, self).__init__(params=params)

    # create the special form for students
    dynafields = [
        {'name': 'organization',
         'base': forms.CharField,
         'label': 'Organization Link ID',
         'widget': widgets.ReadOnlyInput(),
         'required': False,
         },
        ]

    dynaproperties = params_helper.getDynaFields(dynafields)

    student_create_form = dynaform.extendDynaForm(
        dynaform=self._params['create_form'],
        dynaproperties=dynaproperties)

    self._params['student_create_form'] = student_create_form

    # create the special form for public review
    dynafields = [
        {'name': 'comment',
         'base': forms.CharField,
         'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
         'label': 'Comment',
         'required': False,
         'example_text': 'Caution, you will not be able to edit your comment!',
         },
         ]

    dynaproperties = params_helper.getDynaFields(dynafields)
    dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')

    public_review_form = dynaform.newDynaForm(dynamodel=None, 
        dynabase=helper.forms.BaseForm, dynainclude=None, 
        dynaexclude=None, dynaproperties=dynaproperties)
    self._params['public_review_form'] = public_review_form

    # create the special form for mentors
    dynafields = [
        {'name': 'score',
         'base': forms.ChoiceField,
         'label': 'Score',
         'initial': 0,
         'required': False,
         'passthrough': ['initial', 'required', 'choices'],
         'example_text':
             'A score will only be assigned if the review is private!',
         'choices': [(-4,'-4: Wow. This. Sucks.'),
                     (-3,'-3: Needs a lot of work'),
                     (-2,'-2: This is bad'),
                     (-1,'-1: I dont like this'),
                     (0,'0: No score'),
                     (1,'1: Might have potential'),
                     (2,'2: Good'),
                     (3,'3: Almost there'),
                     (4,'4: Made. Of. Awesome.')]
        },
        {'name': 'comment',
         'base': forms.CharField,
         'widget': widgets.FullTinyMCE(attrs={'rows': 10, 'cols': 40}),
         'label': 'Comment',
         'required': False,
         'example_text': 'Caution, you will not be able to edit your review!',
         },
        {'name': 'public',
         'base': forms.BooleanField,
         'label': 'Review visible to Student',
         'initial': False,
         'required': False,
         'help_text': 'By ticking this box the score will not be assigned, '
             'and the review will be visible to the student.',
         },
         ]

    dynaproperties = params_helper.getDynaFields(dynafields)
    dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')

    mentor_review_form = dynaform.newDynaForm(dynamodel=None, 
        dynabase=helper.forms.BaseForm, dynainclude=None, 
        dynaexclude=None, dynaproperties=dynaproperties)
    self._params['mentor_review_form'] = mentor_review_form

    dynafields = [
      {'name': 'rank',
         'base': forms.IntegerField,
         'label': 'Set to rank',
         'help_text':
             'Set this proposal to the given rank (ignores the given score)',
         'example_text': 'A rank will only be assigned if the '
            'review is private!',
         'min_value': 1,
         'required': False,
         'passthrough': ['min_value', 'required', 'help_text'],
      },
      {'name': 'mentor',
       'base': widgets.ReferenceField,
       'passthrough': ['reference_url', 'required', 'label', 'filter'],
       'reference_url': 'mentor',
       'filter': ['__org__'],
       'label': 'Assign Mentor (Link ID)',
       'required': False,
       'help_text': 'Fill in the Link ID of the Mentor '
           'you would like to assign to this Proposal. '
           'Leave this box empty if you don\'t want any mentor assigned.',
      },
      ]

    dynaproperties = params_helper.getDynaFields(dynafields)
    dynaproperties['clean_comment'] = cleaning.clean_html_content('comment')

    admin_review_form = dynaform.extendDynaForm(dynaform=mentor_review_form, 
        dynaproperties=dynaproperties)

    self._params['admin_review_form'] = admin_review_form

  def _editGet(self, request, entity, form):
    """See base.View._editGet().
    """

    form.fields['link_id'].initial = entity.link_id
    form.fields['organization'].initial = entity.org.link_id

    return super(View, self)._editGet(request, entity, form)

  def _editPost(self, request, entity, fields):
    """See base.View._editPost().
    """

    if not entity:
      fields['link_id'] = 't%i' % (int(time.time()*100))
    else:
      fields['link_id'] = entity.link_id

    # fill in the scope via call to super
    super(View, self)._editPost(request, entity, fields)

    if not entity:
      # creating a new application so set the program and org field
      fields['program'] = fields['scope'].scope

      filter = {'scope': fields['program'],
                'link_id': fields['organization']}
      fields['org'] = org_logic.logic.getForFields(filter, unique=True)

    # explicitly change the last_modified_on since the content has been edited
    fields['last_modified_on'] = datetime.datetime.now()

  @decorators.merge_params
  @decorators.check_access
  def public(self, request, access_type,
             page_name=None, params=None, **kwargs):
    """View in which the student can see and reply to the comments on the
       Student Proposal.

    For params see base.view.Public().
    """

    context = helper.responses.getUniversalContext(request)
    helper.responses.useJavaScript(context, params['js_uses_all'])
    context['page_name'] = page_name

    try:
      entity = self._logic.getFromKeyFieldsOr404(kwargs)
    except out_of_band.Error, error:
      return helper.responses.errorResponse(
          error, request, template=params['error_public'], context=context)

    context['entity'] = entity
    context['entity_type'] = params['name']
    context['entity_type_url'] = params['url_name']

    if request.method == 'POST':
      return self.publicPost(request, context, params, entity, **kwargs)
    else: # request.method == 'GET'
      return self.publicGet(request, context, params, entity, **kwargs)

  def publicPost(self, request, context, params, entity, **kwargs):
    """Handles the POST request for the entity's public page.

    Args:
        entity: the student proposal entity
        rest: see base.View.public()
    """

    # populate the form using the POST data
    form = params['public_review_form'](request.POST)

    if not form.is_valid():
      # get some entity specific context
      self.updatePublicContext(context, entity, params)

      # return the invalid form response
      return self._constructResponse(request, entity=entity, context=context,
          form=form, params=params, template=params['public_template'])

    # get the commentary
    fields = form.cleaned_data
    comment = fields['comment']

    if comment:
      # create a new public review containing the comment
      user_entity = user_logic.logic.getForCurrentAccount()

      if user_entity.key() == entity.scope.user.key():
        # student is posting
        reviewer = entity.scope
      else:
        # check if the person commenting is an org_admin
        # or a mentor for the given proposal
        fields = {'user': user_entity,
                  'scope': entity.org,
                  'status': 'active',
                  }

        reviewer = org_admin_logic.logic.getForFields(fields, unique=True)

        if not reviewer:
          # no org_admin found, maybe it's a mentor?
          reviewer = mentor_logic.logic.getForFields(fields, unique=True)

      # create the review (reviewer might be None 
      # if a Host or Developer is posting)
      self._createReviewFor(entity, reviewer, comment, is_public=True)

    # redirect to the same page
    return http.HttpResponseRedirect('')

  def publicGet(self, request, context, params, entity, **kwargs):
    """Handles the GET request for the entity's public page.

    Args:
        entity: the student proposal entity
        rest see base.View.public()
    """

    from soc.logic.models.review_follower import logic as review_follower_logic

    get_dict = request.GET

    if get_dict.get('subscription') and (
      get_dict['subscription'] in ['on', 'off']):

      subscription = get_dict['subscription']

      # get the current user
      user_entity = user_logic.logic.getForCurrentAccount()

      # create the fields that should be in the ReviewFollower entity
      fields = {'link_id': user_entity.link_id,
                'scope': entity,
                'scope_path': entity.key().id_or_name(),
                'user': user_entity
               }
      # get the keyname for the ReviewFollower entity
      key_name = review_follower_logic.getKeyNameFromFields(fields)

      # determine if we should set subscribed_public to True or False
      if subscription == 'on':
        fields['subscribed_public'] = True
      elif subscription == 'off':
        fields['subscribed_public'] = False

      # update the ReviewFollower
      review_follower_logic.updateOrCreateFromKeyName(fields, key_name)

    # get some entity specific context
    self.updatePublicContext(context, entity, params)

    context['form'] = params['public_review_form']()
    template = params['public_template']

    return responses.respond(request, template, context=context)

  def updatePublicContext(self, context, entity, params):
    """Updates the context for the public page with information from the entity.

    Args:
      context: the context that should be updated
      entity: a student proposal_entity used to set context
      params: dict with params for the view using this context
    """

    from soc.logic.models.review import logic as review_logic
    from soc.logic.models.review_follower import logic as review_follower_logic

    student_entity = entity.scope

    context['student'] = student_entity
    context['student_name'] = student_entity.name()

    user_entity = user_logic.logic.getForCurrentAccount()

    # check if the current user is the student
    if user_entity.key() == student_entity.user.key():
      # show the proposal edit link
      context['edit_link'] = redirects.getEditRedirect(entity, params)

    # check if the current user is subscribed to this proposal's public reviews
    fields = {'user': user_entity,
        'scope': entity,
        'subscribed_public': True}

    context['is_subscribed'] = review_follower_logic.getForFields(fields,
                                                                  unique=True)

    context['public_reviews'] = review_logic.getReviewsForEntity(entity,
        is_public=True, order=['created'])

  @decorators.merge_params
  @decorators.check_access
  def apply(self, request, access_type,
             page_name=None, params=None, **kwargs):
    """Special view used to prepopulate the form with the organization
       contributors template.

       For params see base.View.public()
    """
    get_dict = request.GET

    if get_dict.get('organization'):
      # organization chosen, prepopulate with template

      # get the organization
      student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])
      program_entity = student_entity.scope

      filter = {'link_id': get_dict['organization'],
                'scope': program_entity}

      org_entity = org_logic.logic.getForFields(filter, unique=True)

      if org_entity:
        # organization found use special form
        params['create_form'] = params['student_create_form']
        kwargs['content'] = org_entity.contrib_template

    # Create page is an edit page with no key fields
    empty_kwargs = {}
    fields = self._logic.getKeyFieldNames()
    for field in fields:
      empty_kwargs[field] = None

    return super(View, self).edit(request, access_type, page_name=page_name,
                     params=params, seed=kwargs, **empty_kwargs)

  @decorators.merge_params
  @decorators.check_access
  def edit(self, request, access_type,
           page_name=None, params=None, seed=None, **kwargs):
    """If the POST contains (action, Withdraw) the proposal in kwargs
       will be marked as invalid.

    For params see base.View.edit()
    """

    # check if request.POST contains action
    post_dict = request.POST
    if 'action' in post_dict and post_dict['action'] == 'Withdraw':
      # withdraw this proposal
      filter = {'scope_path': kwargs['scope_path'],
                'link_id': kwargs['link_id']}

      proposal_logic = params['logic']
      student_proposal_entity = proposal_logic.getForFields(filter, unique=True)
      reviewer = student_proposal_entity.scope
      
      # update the entity mark it as invalid
      proposal_logic.updateEntityProperties(student_proposal_entity,
          {'status': 'invalid'})

      # redirect to the program's homepage
      redirect_url = redirects.getHomeRedirect(student_proposal_entity.program,
          {'url_name': 'program'})
      
      comment = "Student withdrew proposal."
      self._createReviewFor(student_proposal_entity, reviewer, comment)
      return http.HttpResponseRedirect(redirect_url)

    return super(View, self).edit(request=request, access_type=access_type,
           page_name=page_name, params=params, seed=seed, **kwargs)

  @decorators.merge_params
  @decorators.check_access
  def listOrgs(self, request, access_type,
               page_name=None, params=None, **kwargs):
    """Lists all organization which the given student can propose to.

    For params see base.View.public().
    """

    from soc.views.models import organization as org_view

    student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])

    filter = {'scope': student_entity.scope,
              'status': 'active'}

    list_params = org_view.view.getParams().copy()
    list_params['list_description'] = ('List of %(name_plural)s you can send '
        'your proposal to.') % list_params
    list_params['list_action'] = (redirects.getStudentProposalRedirect,
        {'student_key': student_entity.key().id_or_name(),
            'url_name': params['url_name']})

    return self.list(request, access_type=access_type, page_name=page_name,
                     params=list_params, filter=filter, **kwargs)

  @decorators.merge_params
  @decorators.check_access
  def listSelf(self, request, access_type,
               page_name=None, params=None, **kwargs):
    """Lists all proposals from the current logged-in user 
       for the given student.

    For params see base.View.public().
    """

    context = {}
    student_entity = student_logic.logic.getFromKeyName(kwargs['scope_path'])

    filter = {'scope' : student_entity,
              'status': ['new', 'pending', 'accepted', 'rejected']}

    list_params = params.copy()
    list_params['list_description'] = \
        'List of my %(name_plural)s.' % list_params
    list_params['list_action'] = (redirects.getPublicRedirect, list_params)

    valid_list = lists.getListContent(
        request, list_params, filter, idx=0)

    ip_params = list_params.copy() # ineligible proposals

    description = ugettext('List of my ineligible/withdrawn %s.') % (
        ip_params['name_plural'])

    ip_params['list_description'] = description
    ip_params['list_action'] = (redirects.getPublicRedirect, ip_params)

    filter = {'scope' : student_entity,
              'status': 'invalid'}

    ip_list = lists.getListContent(
        request, ip_params, filter, idx=1, need_content=True)

    contents = []
    # fill contents with all the needed lists
    contents.append(valid_list)

    if ip_list != None:
      contents.append(ip_list)

    # call the _list method from base to display the list
    return self._list(request, list_params, contents, page_name, context)

  @decorators.merge_params
  @decorators.check_access
  def review(self, request, access_type,
             page_name=None, params=None, **kwargs):
    """View that allows Organization Admins and Mentors to review the proposal.

       For Args see base.View.public().
    """

    try:
      entity = self._logic.getFromKeyFieldsOr404(kwargs)
    except out_of_band.Error, error:
      return helper.responses.errorResponse(
          error, request, template=params['error_public'])

    # get the context for this webpage
    context = responses.getUniversalContext(request)
    responses.useJavaScript(context, params['js_uses_all'])
    context['page_name'] = '%s "%s" from %s' % (page_name, entity.title,
                                                entity.scope.name())
    context['entity'] = entity
    context['entity_type'] = params['name']
    context['entity_type_url'] = params['url_name']

    # get the roles important for reviewing an application
    filter = {'user': user_logic.logic.getForCurrentAccount(),
        'scope': entity.org,
        'status': 'active'}

    org_admin_entity = org_admin_logic.logic.getForFields(filter, unique=True)
    mentor_entity = mentor_logic.logic.getForFields(filter, unique=True)

    # decide which form to use
    if org_admin_entity:
      form = params['admin_review_form']
    else:
      form = params['mentor_review_form']

    if request.method == 'POST':
      return self.reviewPost(request, context, params, entity,
                             form, org_admin_entity, mentor_entity, **kwargs)
    else:
      # request.method == 'GET'
      return self.reviewGet(request, context, params, entity,
                            form, org_admin_entity, mentor_entity, **kwargs)

  def reviewPost(self, request, context, params, entity, form,
                 org_admin, mentor, **kwargs):
    """Handles the POST request for the proposal review view.

    Args:
        entity: the student proposal entity
        form: the form to use in this view
        org_admin: org admin entity for the current user/proposal (iff available)
        mentor: mentor entity for the current user/proposal (iff available)
        rest: see base.View.public()
    """
    # populate the form using the POST data
    form = form(request.POST)

    if not form.is_valid():
      # return the invalid form response
      # get all the extra information that should be in the context
      review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
      context = dicts.merge(context, review_context)

      return self._constructResponse(request, entity=entity, context=context,
          form=form, params=params, template=params['review_template'])

    fields = form.cleaned_data
    is_public = fields['public']
    comment = fields['comment']
    given_score = int(fields['score'])

    if org_admin:
      # org admin found, try to adjust the assigned mentor
      self._adjustMentor(entity, fields['mentor'])
      reviewer = org_admin

      # try to see if the rank is given and adjust the given_score if needed
      rank = fields['rank']
      if rank:
        ranker = self._logic.getRankerFor(entity)
        # if a very high rank is filled in use the highest 
        # one that returns a score
        rank = min(ranker.TotalRankedScores(), rank)
        # ranker uses zero-based ranking
        score_and_rank = ranker.FindScore(rank-1)
        # get the score at the requested rank
        score_at_rank = score_and_rank[0][0]
        # calculate the score that should be given to end up at the given rank
        # give +1 to make sure that in the case of a tie they end up top
        given_score = score_at_rank - entity.score + 1
    else:
      # might be None (if Host or Developer is commenting)
      reviewer = mentor

    # store the properties to update the proposal with
    properties = {}

    if reviewer and (not is_public) and (given_score is not 0):
      # if it is not a public comment and it's made by a member of the
      # organization we update the score of the proposal
      new_score = given_score + entity.score
      properties = {'score': new_score}

    if comment or (given_score is not 0):
      # if the proposal is new we change it status to pending
      if entity.status == 'new':
        properties['status'] = 'pending'

      # create the review entity
      self._createReviewFor(entity, reviewer, comment, given_score, is_public)

    if properties.values():
      # there is something to update
      self._logic.updateEntityProperties(entity, properties)

    # redirect to the same page
    return http.HttpResponseRedirect('')

  def reviewGet(self, request, context, params, entity, form,
                 org_admin, mentor, **kwargs):
    """Handles the GET request for the proposal review view.

    Args:
        entity: the student proposal entity
        form: the form to use in this view
        org_admin: org admin entity for the current user/proposal (iff available)
        mentor: mentor entity for the current user/proposal (iff available)
        rest: see base.View.public()
    """

    from soc.logic.models.review_follower import logic as review_follower_logic

    get_dict = request.GET

    # check if the current user is a mentor and wants 
    # to change his role for this app
    choice = get_dict.get('mentor')
    if mentor and choice:
      self._adjustPossibleMentors(entity, mentor, choice)

    ineligible = get_dict.get('ineligible')
    
    if org_admin:
      reviewer = org_admin
    elif mentor:
      reviewer = mentor
    
    if (org_admin or mentor) and ineligible != None:
      ineligible = int(ineligible)
      if ineligible == 1:
        # mark the proposal invalid and return to the list
        properties = {'status': 'invalid'}
        self._logic.updateEntityProperties(entity, properties)

        redirect = redirects.getListProposalsRedirect(entity.org,
                                                      {'url_name': 'org'})
        comment = "Marked Student Proposal as Ineligible."
        self._createReviewFor(entity, reviewer, comment, is_public=False)
        return http.HttpResponseRedirect(redirect)
      elif ineligible == 0:
        # mark the proposal as new and return to the list
        properties = {'status': 'new'}
        self._logic.updateEntityProperties(entity, properties)

        redirect = redirects.getListProposalsRedirect(entity.org,
                                                      {'url_name': 'org'})
        comment = "Marked Student Proposal as Eligible."
        self._createReviewFor(entity, reviewer, comment, is_public=False)
        return http.HttpResponseRedirect(redirect)

    # check if we should change the subscription state for the current user
    public_subscription = None
    private_subscription = None

    if get_dict.get('public_subscription') and (
      get_dict['public_subscription'] in ['on', 'off']):

      public_subscription = get_dict['public_subscription'] == 'on'

    if get_dict.get('private_subscription') and (
      get_dict['private_subscription'] in ['on', 'off']):
      private_subscription = get_dict['private_subscription'] == 'on'

    if public_subscription != None or private_subscription != None:
      # get the current user
      user_entity = user_logic.logic.getForCurrentAccount()

      # create the fields that should be in the ReviewFollower entity
      fields = {'link_id': user_entity.link_id,
                'scope': entity,
                'scope_path': entity.key().id_or_name(),
                'user': user_entity
               }
      # get the keyname for the ReviewFollower entity
      key_name = review_follower_logic.getKeyNameFromFields(fields)

      # determine which subscription properties we should change
      if public_subscription != None:
        fields['subscribed_public'] = public_subscription

      if private_subscription != None:
        fields['subscribed_private'] = private_subscription

      # update the ReviewFollower
      review_follower_logic.updateOrCreateFromKeyName(fields, key_name)

    # set the initial score since the default is ignored
    initial = {'score': 0}

    if org_admin and entity.mentor:
      # set the mentor field to the current mentor
      initial['mentor'] = entity.mentor.link_id

    context['form'] = form(initial)

    # create the special form for mentors
    comment_public = ['public', 'comment']
    comment_private = ['score']
    comment_admin = ['rank', 'mentor']
    class FilterForm(object):
      """Helper class used for form filtering.
      """
      def __init__(self, form, fields):
        self.__form = form
        self.__fields = fields

      @property
      def fields(self):
        """Property that returns all fields as dictionary."""
        fields = self.__form.fields.iteritems()
        return dict([(k, i) for k, i in fields if k in self.__fields])

      def __iter__(self):
        for field in self.__form:
          if field.name not in self.__fields:
            continue
          yield field

      _marker = []
      def __getattr__(self, key, default=_marker):
        if default is self._marker:
          return getattr(self.__form, key)
        else:
          return getattr(self.__form, key, default)

    context['form'] = form(initial)
    context['comment_public'] = FilterForm(context['form'], comment_public)
    context['comment_private'] = FilterForm(context['form'], comment_private)
    context['comment_admin'] = FilterForm(context['form'], comment_admin)

    # get all the extra information that should be in the context
    review_context = self._getDefaultReviewContext(entity, org_admin, mentor)
    context = dicts.merge(context, review_context)

    template = params['review_template']

    return responses.respond(request, template, context=context)

  def _getDefaultReviewContext(self, entity, org_admin,
                               mentor):
    """Returns the default context for the review page.

    Args:
      entity: Student Proposal entity
      org_admin: org admin entity for the current user/proposal (iff available)
      mentor: mentor entity for the current user/proposal (iff available)
    """

    from soc.logic.models.review import logic as review_logic
    from soc.logic.models.review_follower import logic as review_follower_logic

    context = {}

    context['student'] = entity.scope
    context['student_name'] = entity.scope.name()

    if entity.mentor:
      context['mentor_name'] = entity.mentor.name()
    else:
      context['mentor_name'] = "No mentor assigned"

    # set the possible mentors in the context
    possible_mentors = entity.possible_mentors

    if not possible_mentors:
      context['possible_mentors'] = "None"
    else:
      mentor_names = []

      for mentor_key in possible_mentors:
        possible_mentor = mentor_logic.logic.getFromKeyName(mentor_key.id_or_name())
        mentor_names.append(possible_mentor.id_or_name())

      context['possible_mentors'] = ', '.join(mentor_names)

    # order the reviews by ascending creation date
    order = ['created']

    # get the public reviews
    public_reviews = review_logic.getReviewsForEntity(entity,
        is_public=True, order=order)

    # get the private reviews
    private_reviews = review_logic.getReviewsForEntity(entity,
        is_public=False, order=order)

    # store the reviews in the context
    context['public_reviews'] = public_reviews
    context['private_reviews'] = private_reviews

    # create a summary of all the private reviews
    review_summary = {}

    for review in private_reviews:
      # make sure there is a reviewer
      reviewer = review.reviewer
      if not reviewer:
        continue

      reviewer_key = reviewer.key()
      reviewer_summary = review_summary.get(reviewer_key)

      if reviewer_summary:
        # we already have something on file for this reviewer
        old_total_score = reviewer_summary['total_score']
        reviewer_summary['total_score'] = old_total_score + review.score

        old_total_comments = reviewer_summary['total_comments']
        reviewer_summary['total_comments'] = old_total_comments + 1
      else:
        review_summary[reviewer_key] = {
            'name': reviewer.name(),
            'total_comments': 1,
            'total_score': review.score}

    context['review_summary'] = review_summary

    # which button should we show to the mentor?
    if mentor:
      context['is_mentor'] = True
      if mentor.key() in possible_mentors:
        # show "No longer willing to mentor"
        context['remove_me_as_mentor'] = True
      else:
        # show "I am willing to mentor"
        context['add_me_as_mentor'] = True

    if org_admin:
      context['is_org_admin'] = True

    user_entity = user_logic.logic.getForCurrentAccount()

    # check if the current user is subscribed to public or private reviews
    fields = {'scope': entity,
              'user': user_entity,}
    follower_entity = review_follower_logic.getForFields(fields, unique=True)

    if follower_entity:
      context['is_subscribed_public'] =  follower_entity.subscribed_public
      context['is_subscribed_private'] = follower_entity.subscribed_private

    return context

  def _adjustPossibleMentors(self, entity, mentor, choice):
    """Adjusts the possible mentors list for a proposal.

    Args:
      entity: Student Proposal entity
      mentor: Mentor entity
      choice: 1 means want to mentor, 0 do not want to mentor
    """
    possible_mentors = entity.possible_mentors

    if choice == '1':
      # add the mentor to possible mentors list if not already in
      if mentor.key() not in possible_mentors:
        possible_mentors.append(mentor.key())
        fields = {'possible_mentors': possible_mentors}
        self._logic.updateEntityProperties(entity, fields)
    elif choice == '0':
      # remove the mentor from the possible mentors list
      if mentor.key() in possible_mentors:
        possible_mentors.remove(mentor.key())
        fields = {'possible_mentors': possible_mentors}
        self._logic.updateEntityProperties(entity, fields)

  def _adjustMentor(self, entity, mentor_id):
    """Changes the mentor to the given link_id.

    Args:
      entity: Student Proposal entity
      mentor_id: Link ID of the mentor that needs to be assigned
                 Iff not given then removes the assigned mentor
    """

    if entity.mentor and entity.mentor.link_id == mentor_id:
      # no need to change
      return

    if mentor_id:
      # try to locate the mentor
      fields = {'link_id': mentor_id,
                'scope': entity.org,
                'status': 'active'}

      mentor_entity = mentor_logic.logic.getForFields(fields, unique=True)

      if not mentor_entity:
        # no mentor found, do not update
        return
    else:
      # reset to None
      mentor_entity = None

    # update the proposal
    properties = {'mentor': mentor_entity}
    self._logic.updateEntityProperties(entity, properties)

  def _createReviewFor(self, entity, reviewer, comment, 
                       score=0, is_public=True):
    """Creates a review for the given proposal and sends 
       out a message to all followers.

    Args:
      entity: Student Proposal entity for which the review should be created
      reviewer: A role entity of the reviewer (if possible, else None)
      comment: The textual contents of the review
      score: The score of the review (only used if the review is not public)
      is_public: Determines if the review is a public review
    """

    from soc.logic.helper import notifications as notifications_helper
    from soc.logic.models.review import logic as review_logic
    from soc.logic.models.review_follower import logic as review_follower_logic

    # create the fields for the review entity
    fields = {'link_id': 't%i' % (int(time.time()*100)),
        'scope': entity,
        'scope_path': entity.key().id_or_name(),
        'author': user_logic.logic.getForCurrentAccount(),
        'content': comment,
        'is_public': is_public,
        'reviewer': reviewer
        }

    # add the given score if the review is not public
    if not is_public:
      fields['score'] = score

    # create a new Review
    key_name = review_logic.getKeyNameFromFields(fields)
    review_entity = review_logic.updateOrCreateFromKeyName(fields, key_name)

    # get all followers
    fields = {'scope': entity}

    if is_public:
      fields['subscribed_public'] = True
    else:
      fields['subscribed_private'] = True

    followers = review_follower_logic.getForFields(fields)

    if is_public:
      # redirect to public page
      redirect_url = redirects.getPublicRedirect(entity, self._params)
    else:
      # redirect to review page
      redirect_url = redirects.getReviewRedirect(entity, self._params)

    for follower in followers:
      # sent to every follower except the reviewer
      if follower.user.key() != review_entity.author.key():
        notifications_helper.sendNewReviewNotification(follower.user,
            review_entity, entity.title, redirect_url)


view = View()

admin = decorators.view(view.admin)
apply = decorators.view(view.apply)
create = decorators.view(view.create)
delete = decorators.view(view.delete)
edit = decorators.view(view.edit)
list = decorators.view(view.list)
list_orgs = decorators.view(view.listOrgs)
list_self = decorators.view(view.listSelf)
public = decorators.view(view.public)
review = decorators.view(view.review)
export = decorators.view(view.export)
pick = decorators.view(view.pick)