app/soc/views/models/student_proposal.py
author Lennard de Rijk <ljvderijk@gmail.com>
Sun, 08 Mar 2009 13:14:28 +0000
changeset 1741 0da1285f5bc0
parent 1715 3ec1a9518452
child 1752 255117ccd6a0
permissions -rw-r--r--
Public reviews are now shown on the public page for the student proposal. The assigned mentor name has been removed from the public page. The student proposal view now uses the new getReviewsForEntity method. Patch by: Lennard de Rijk Reviewed by:to-be-reviewed

#!/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 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 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'),
        ('checkRoleAndStatusForStudentProposal',
            [['proposer'], ['active'], ['new', 'pending']])]
    rights['delete'] = ['checkIsDeveloper']
    rights['show'] = [
        ('checkRoleAndStatusForStudentProposal',
            [['proposer', 'org_admin', 'mentor', 'host'], 
            ['active', 'inactive'], 
            ['new', 'pending', 'accepted', 'rejected']])]
    rights['list'] = ['checkIsDeveloper']
    rights['list_orgs'] = [
        ('checkIsStudent', ['scope_path', ['active']]),
        ('checkCanStudentPropose', 'scope_path')]
    rights['list_self'] = [
        ('checkIsStudent', ['scope_path', ['active', 'inactive']])]
    rights['apply'] = [
        ('checkIsStudent', ['scope_path', ['active']]),
        ('checkCanStudentPropose', 'scope_path')]
    rights['review'] = [('checkRoleAndStatusForStudentProposal',
            [['org_admin', 'mentor', 'host'], 
            ['active'], ['new', 'pending']])]

    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'] = 'Student Proposal'

    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_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)

    params['student_create_form'] = student_create_form

    # create the special form for mentors
    dynafields = [
        {'name': 'score',
         'base': forms.ChoiceField,
         'label': 'Score',
         'initial': 0,
         'required': False,
         'passthrough': ['initial', 'required', 'choices'],
         '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,
         },
        {'name': 'public',
         'base': forms.BooleanField,
         'label': 'Public comment',
         'initial': False,
         'required': False,
         },
         ]

    dynaproperties = params_helper.getDynaFields(dynafields)

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

    # TODO see if autocomplete can be used for this field
    dynafields = [
      {'name': 'mentor',
       'base': forms.CharField,
       'label': 'Assign Mentor (Link ID)',
       'required': False
      },
      ]

    dynaproperties = params_helper.getDynaFields(dynafields)

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

    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' % (time.time())
    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()

  def _public(self, request, entity, context):
    """See base.View._public().
    """

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

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

    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)

      # 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'})

      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().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().
    """

    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

    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 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'] = page_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)

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

    # 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

    if org_admin:
      # org admin found, try to adjust the assigned mentor
      self._adjustMentor(entity, fields['mentor'])
      reviewer = org_admin
    else:
      # might be None (if Host or Developer is commenting)
      reviewer = mentor

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

    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 the proposal is new we change it status to pending
      if entity.status == 'new':
        properties['status'] = 'pending'

      # update the proposal with the new score
      self._logic.updateEntityProperties(entity, properties)

    # create the review entity
    if comment or (given_score is not 0):
      self._createReviewFor(entity, reviewer, comment, given_score, is_public)

    # 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()
    """

    # 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)

    # 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

    context = {}

    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.name())
        mentor_names.append(possible_mentor.name())

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

    # TODO(ljvderijk) listing of total given scores per mentor
    # a dict with key as role.user ?

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

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

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

    # which button should we show to the mentor?
    if mentor:
      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

    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, is_public):
    """Creates a review for the given proposal.

    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

    Returns:
      - The newly created review
    """

    import time

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

    # create the fields for the review entity
    fields = {'link_id': 't%i' %(time.time()),
        'scope': entity,
        'scope_path': entity.key().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

    key_name = review_logic.getKeyNameFromFields(fields)

    return review_logic.updateOrCreateFromKeyName(fields, key_name)


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)