blob: 9d9a0f70caa78b8d198a3530947a36b327242d17 [file] [log] [blame]
# Copyright 2014 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.
"""Module containing the views to manage proposals for Summer Of Code."""
import collections
import json
from google.appengine.ext import ndb
from django import forms as django_forms
from django import http
from django.utils import translation
from melange.logic import profile as profile_logic
from melange.request import access
from melange.request import exception
from melange.request import links as melange_links
from melange.models import profile as profile_model
from soc.modules.gsoc.views import assign_mentor
from soc.modules.gsoc.views import forms as gsoc_forms
from soc.modules.gsoc.views import base as gsoc_base
from soc.modules.gsoc.views.helper import url_patterns as soc_url_patterns
from soc.views import base
from soc.views import template
from soc.views import toggle_button
from soc.views.helper import url_patterns
from summerofcode.logic import profile as soc_profile_logic
from summerofcode.logic import proposal as proposal_logic
from summerofcode.models import proposal as proposal_model
from summerofcode.request import error
from summerofcode.request import links
from summerofcode.request import render
from summerofcode.templates import proposal_revision_list
from summerofcode.templates import sidebar_actions
from summerofcode.templates import top_message
from summerofcode.views.helper import urls as soc_urls
_PROPOSAL_PUBLIC_PAGE_NAME = translation.ugettext('View Proposal')
_PROPOSAL_REVIEW_PAGE_NAME = translation.ugettext('Review Proposal')
_LIST_PROPOSAL_REVISIONS_PAGE_NAME = translation.ugettext(
'List proposal revisions')
_PROPOSAL_REVISION_REVIEW_PAGE_NAME = translation.ugettext(
'Review Proposal Revision')
_MANAGE_ACTIONS_TITLE = translation.ugettext('Proposal Actions')
_COMMENT_FORM_GROUP = translation.ugettext('Leave a comment')
_COMMENT_FORM_IS_PRIVATE_HELP_TEXT = translation.ugettext(
'Private comments are visible to organization members only. '
'Non-private comments are also be visible to the student '
'who is the author of this proposal.')
_COMMENT_FORM_IS_PRIVATE_LABEL = translation.ugettext('Private')
_MESSAGE_CURRENT_USER_NOT_MENTOR = translation.ugettext(
'The currently logged in user is not a mentor for the organization '
'to which the proposal has been submitted.')
_MESSAGE_PROPOSAL_NOT_PUBLIC = translation.ugettext(
'This proposal is not visible publicly.')
_MESSAGE_PROPOSAL_IGNORED = translation.ugettext(
'This proposal is ignored.')
_MESSAGE_CANNOT_ACCEPT_PROPOSAL = translation.ugettext(
'This proposal cannot be accepted.')
_MESSAGE_USERS_NOT_MENTORS = translation.ugettext(
'%s are not a mentors for the organization to which '
'the proposal has been submitted.')
_BUTTON_ACCEPT_AS_PROJECT_HELP_TEXT = translation.ugettext(
'Choosing Yes will mark this proposal as accepted. The proposal is '
'accepted when Yes is displayed in bright orange.')
_BUTTON_ACCEPT_AS_PROJECT_TITLE = translation.ugettext('Accept proposal')
_BUTTON_ACCEPT_AS_PROJECT_ID = 'accept-proposal'
_BUTTON_IGNORE_HELP_TEXT = translation.ugettext(
'Choosing Yes will mark this proposal as ignored. The student will be '
'be able to see that this proposal is ignored when he/she visits this '
'page. The proposal is ignored when Yes is displayed in bright orange.')
_BUTTON_IGNORE_TITLE = translation.ugettext('Ignore')
_BUTTON_IGNORE_ID = translation.ugettext('ignore')
_BUTTON_WISH_TO_MENTOR_HELP_TEXT = translation.ugettext(
'Choosing Yes will add your name to the list of possible mentors to '
'this proposal. You will be listed as a possible mentor when Yes is '
'displayed in bright orange.')
_BUTTON_EDITABLE_POST_DEADLINE_TITLE = translation.ugettext(
'Editable post deadline')
_BUTTON_EDITABLE_POST_DEADLINE_ID = 'editable-post-deadline'
_BUTTON_WISH_TO_MENTOR_TITLE = translation.ugettext('Wish to mentor')
_BUTTON_EDITABLE_POST_DEADLINE_HELP_TEXT = translation.ugettext(
'Choosing Yes allows the student to edit this proposal after the '
'submission period is over.')
_BUTTON_WITH_TO_MENTOR_ID = translation.ugettext('wish-to-mentor')
_BUTTON_WITHDRAW_TITLE = translation.ugettext('Withdraw')
_BUTTON_WITHDRAW_ID = 'withdraw'
_BUTTON_WITHDRAW_HELP_TEXT = translation.ugettext(
'Choosing Yes, notifies your organization that you have withdrawn '
'this proposal and no longer wish to participate in the program with '
'this proposal. The proposal is withdrawn when the button displays '
'Yes in bright orange.')
class _CommentForm(gsoc_forms.GSoCModelForm):
"""Form to post comments for proposals."""
Meta = object
is_private = django_forms.BooleanField(
required=False, label=_COMMENT_FORM_IS_PRIVATE_LABEL,
help_text=_COMMENT_FORM_IS_PRIVATE_HELP_TEXT,
initial=True)
content = django_forms.CharField(
required=True, widget=django_forms.widgets.Textarea())
def __init__(self, *args, **kwargs):
"""Initializes a new instance of the form."""
super(_CommentForm, self).__init__(*args, **kwargs)
for _, field in self.fields.iteritems():
field.group = _COMMENT_FORM_GROUP
# TODO(daniel): add clean for content
def templatePath(self):
return 'modules/gsoc/proposal/_comment_form.html'
def _commentFormToReviewAsOrgMember(**kwargs):
"""Returns a Django form to leave a comment as an organization member.
Returns:
_CommentForm adjusted to leave a comment as an organization member.
"""
return _CommentForm(**kwargs)
def _commentFormToReviewAsStudent(**kwargs):
"""Returns a Django form to leave a comment as a student.
Returns:
_CommentForm adjusted to leave a comment as a student.
"""
form = _CommentForm(**kwargs)
# student is allowed to post public comments only.
del form.fields['is_private']
return form
DuplicateProposals = collections.namedtuple(
'DuplicateProposals', ['org_name', 'org_url', 'org_admins', 'title'])
class DuplicateData(template.Template):
"""Template for showing information regarding a duplicate proposal."""
def context(self):
"""See template.Template.context for specification."""
proposals = sorted(
ndb.get_multi(
self.data.url_ndb_profile.student_data.to_be_accepted_proposals),
key=lambda proposal: proposal_logic.getProposalRank(proposal))
duplicate_proposals = []
for proposal in proposals:
org = proposal.organization.get()
org_admins = profile_logic.getOrgAdmins(org)
org_url = links.SOC_LINKER.organization(
org.key, soc_urls.UrlNames.ORG_HOME)
duplicate_proposals.append(
DuplicateProposals(org.name, org_url, org_admins, proposal.title))
return {'duplicate_proposals': duplicate_proposals}
def templatePath(self):
return 'summerofcode/proposal/_duplicate_data.html'
def _toggleButtonsForStudent(data, url_names):
"""Returns toggle buttons for proposal actions available for students.
Args:
data: request_data.RequestData for the current request.
url_names: Instance of url_names.UrlNames.
Returns:
A list of toggle_button.ToggleButtonTwoActionsTemplate instances.
"""
toggle_buttons = []
if not data.timeline.studentsAnnounced():
is_withdrawn = (
data.url_ndb_proposal.status == proposal_model.Status.WITHDRAWN)
toggle_buttons.append(
toggle_button.ToggleButtonTwoActionsTemplate(
data, 'on_off', _BUTTON_WITHDRAW_TITLE, _BUTTON_WITHDRAW_ID,
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_WITHDRAW),
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_RESUBMIT),
checked=is_withdrawn, labels={'checked': 'Yes', 'unchecked': 'No'},
help_text=_BUTTON_WITHDRAW_HELP_TEXT))
return toggle_buttons
def _toggleButtonsForMentor(data, url_names):
"""Returns toggle buttons for proposal actions available for mentors.
Args:
data: request_data.RequestData for the current request.
url_names: Instance of url_names.UrlNames.
Returns:
A list of toggle_button.ToggleButtonTwoActionsTemplate instances.
"""
toggle_buttons = []
toggle_buttons.append(
toggle_button.ToggleButtonTwoActionsTemplate(
data, 'on_off', _BUTTON_WISH_TO_MENTOR_TITLE,
_BUTTON_WITH_TO_MENTOR_ID,
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_POSSIBLE_MENTOR_ADD),
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_POSSIBLE_MENTOR_REMOVE),
checked=(
data.ndb_profile.key in data.url_ndb_proposal.possible_mentors),
labels={'checked': 'Yes', 'unchecked': 'No'},
help_text=_BUTTON_WISH_TO_MENTOR_HELP_TEXT))
toggle_buttons.append(
toggle_button.ToggleButtonTwoActionsTemplate(
data, 'on_off', _BUTTON_EDITABLE_POST_DEADLINE_TITLE,
_BUTTON_EDITABLE_POST_DEADLINE_ID,
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_EDITABLE_POST_DEADLINE),
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_UNEDITABLE_POST_DEADLINE),
checked=data.url_ndb_proposal.is_editable_post_deadline,
labels={'checked': 'Yes', 'unchecked': 'No'},
help_text=_BUTTON_EDITABLE_POST_DEADLINE_HELP_TEXT))
return toggle_buttons
def _toggleButtonsForAdmin(data, url_names):
"""Returns toggle buttons for proposal actions available for organization
administrators and program administrators.
Args:
data: request_data.RequestData for the current request.
url_names: Instance of url_names.UrlNames.
Returns:
A list of toggle_button.ToggleButtonTwoActionsTemplate instances.
"""
toggle_buttons = []
toggle_buttons.append(
toggle_button.ToggleButtonTwoActionsTemplate(
data, 'on_off', _BUTTON_ACCEPT_AS_PROJECT_TITLE,
_BUTTON_ACCEPT_AS_PROJECT_ID,
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_ACCEPT_AS_PROJECT),
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_REJECT_AS_PROJECT),
checked=data.url_ndb_proposal.accept_as_project,
labels={'checked': 'Yes', 'unchecked': 'No'},
help_text=_BUTTON_ACCEPT_AS_PROJECT_HELP_TEXT))
toggle_buttons.append(
toggle_button.ToggleButtonTwoActionsTemplate(
data, 'on_off', _BUTTON_IGNORE_TITLE, _BUTTON_IGNORE_ID,
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_IGNORE),
links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(),
data.url_ndb_proposal.key.id(),
url_names.PROPOSAL_UNIGNORE),
checked=data.url_ndb_proposal.is_ignored,
labels={'checked': 'Yes', 'unchecked': 'No'},
help_text=_BUTTON_IGNORE_HELP_TEXT))
return toggle_buttons
_PROPOSAL_STATUS_CHOICES = [
('accepted', translation.ugettext('Accept')),
]
class SetStatusField(template.Template):
"""Template to render the fields needed to set status of the entity."""
def __init__(self, data, action):
"""Instantiates the template for Set Proposal Status field.
data: The request data object
action: The form action URL to which the form should be posted
"""
super(SetStatusField, self).__init__(data)
self.action = action
def context(self):
"""See template.Template.context for specification."""
return {
'action': self.action,
'status_choices': _PROPOSAL_STATUS_CHOICES
}
def templatePath(self):
return 'summerofcode/proposal/_set_status.html'
class IsUrlProposalPubliclyVisibleAccessChecker(access.AccessChecker):
"""AccessChecker that ensures that the proposal which is defined in the URL
is publicly visible.
"""
def checkAccess(self, data, check):
"""See AccessChecker.checkAccess for specification."""
if data.url_ndb_proposal.visibility != proposal_model.Visibility.PUBLIC:
raise exception.Forbidden(message=_MESSAGE_PROPOSAL_NOT_PUBLIC)
class IsUrlProposalVisibleForStudentAccessChecker(access.AccessChecker):
"""AccessChecker that ensures that the proposal which is defined in the URL
is visible for other students in the program and that the accessing viewer
is a student.
"""
def checkAccess(self, data, check):
"""See AccessChecker.checkAccess for specification."""
if (data.url_ndb_proposal.visibility !=
proposal_model.Visibility.STUDENT):
raise exception.Forbidden(message=_MESSAGE_PROPOSAL_NOT_PUBLIC)
if (not data.ndb_profile or not data.ndb_profile.is_student
or data.ndb_profile.status != profile_model.Status.ACTIVE):
raise exception.Forbidden(message=_MESSAGE_PROPOSAL_NOT_PUBLIC)
class ProposalPublicPage(base.RequestHandler):
"""View to display proposal publicly."""
access_checker = access.DisjunctionAccessChecker([
IsUrlProposalPubliclyVisibleAccessChecker(),
IsUrlProposalVisibleForStudentAccessChecker(),
access.PROGRAM_ADMINISTRATOR_ACCESS_CHECKER])
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names, template_path):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
template_path: The path of the template to be used.
"""
super(ProposalPublicPage, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
self.template_path = template_path
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/public/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_PUBLIC),
]
def templatePath(self):
"""See base.RequestHandler.templatePath for specification."""
return self.template_path
def context(self, data, check, mutator):
"""See base.RequestHandler.context for specification."""
return {
'abstract': data.url_ndb_proposal.abstract,
'additional_info': data.url_ndb_proposal.additional_info,
'content': data.url_ndb_proposal.content,
'page_name': _PROPOSAL_PUBLIC_PAGE_NAME,
'student_name': data.url_ndb_proposal.key.parent().get().public_name,
'title': data.url_ndb_proposal.title,
}
PROPOSAL_PUBLIC_PAGE = ProposalPublicPage(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames, 'summerofcode/proposal/proposal_public.html')
class ListProposalRevisions(base.RequestHandler):
"""View to list all proposal revisions of a particular proposal."""
access_checker = access.ALL_ALLOWED_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names, template_path):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
template_path: The path of the template to be used.
"""
super(ListProposalRevisions, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
self.template_path = template_path
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/revisions/list/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_REVISIONS_LIST),
self.url_pattern_constructor.construct(
r'proposal/revisions/list/%s$' % soc_url_patterns.PROPOSAL_REVISIONS,
self, name=self.url_names.PROPOSAL_REVISIONS_LIST),
]
def templatePath(self):
"""See base.RequestHandler.templatePath for specification."""
return self.template_path
def context(self, data, check, mutator):
"""See base.RequestHandler.context for specification."""
# 'revision1' and 'revision2' represent the two selected
# proposal revisions to be diffed. They have no particular
# order as they are fetched from the list post data.
if 'revision1' and 'revision2' in data.kwargs:
revision1 = proposal_model.ProposalRevision.get_by_id(
data.kwargs['revision1'], parent=data.url_ndb_proposal.key)
revision2 = proposal_model.ProposalRevision.get_by_id(
data.kwargs['revision2'], parent=data.url_ndb_proposal.key)
if revision2.revision > revision1.revision:
original_revision = revision1
changed_revision = revision2
else:
original_revision = revision2
changed_revision = revision1
else:
original_revision = None
changed_revision = None
return {
'list': proposal_revision_list.getProposalRevisionList(
data, data.url_ndb_proposal.key),
'page_name': _LIST_PROPOSAL_REVISIONS_PAGE_NAME,
'original_revision': original_revision,
'changed_revision': changed_revision,
}
def jsonContext(self, data, check, mutator):
"""See base.RequestHandler.jsonContext for specification."""
list_data = proposal_revision_list.getProposalRevisionList(
data, data.url_ndb_proposal.key).getListData()
if list_data:
return list_data.content()
else:
raise exception.BadRequest(message='This data cannot be accessed.')
def post(self, data, check, mutator):
list_content = proposal_revision_list.getProposalRevisionList(
data, data.url_ndb_proposal.key)
result = list_content.post()
revision1_key = ndb.Key(urlsafe=result.extra['one'])
revision2_key = ndb.Key(urlsafe=result.extra['two'])
if result:
diff_url = links.SOC_LINKER.proposalRevisionsId(
data.url_ndb_proposal.key, revision1_key, revision2_key,
data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_REVISIONS_LIST)
return http.HttpResponse(json.dumps({'data':{'url':diff_url}}))
else:
raise exception.BadRequest(message=result.extra)
LIST_PROPOSAL_REVISIONS = ListProposalRevisions(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames, 'summerofcode/proposal/proposal_revisions_list.html')
class ProposalRevisionReviewPage(base.RequestHandler):
"""View to review a proposal revision."""
access_checker = access.ALL_ALLOWED_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names, template_path):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
template_path: The path of the template to be used.
"""
super(ProposalRevisionReviewPage, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
self.template_path = template_path
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/revision/review/%s$' % soc_url_patterns.PROPOSAL_REVISION,
self, name=self.url_names.PROPOSAL_REVISION_REVIEW),
]
def templatePath(self):
"""See base.RequestHandler.templatePath for specification."""
return self.template_path
def context(self, data, check, mutator):
"""See base.RequestHandler.context for specification."""
proposal = data.url_ndb_proposal_revision.key.parent().get()
prev_revision_url = None
next_revision_url = None
if int(data.url_ndb_proposal_revision.key.id()) > 0:
prev_revision_url = melange_links.LINKER.proposalRevisionId(
proposal.key, str(int(data.url_ndb_proposal_revision.key.id()) - 1),
soc_urls.UrlNames.PROPOSAL_REVISION_REVIEW)
total_revisions = proposal_model.ProposalRevision.query(
ancestor=proposal.key).count()
if int(data.url_ndb_proposal_revision.key.id()) + 1 < total_revisions:
next_revision_url = melange_links.LINKER.proposalRevisionId(
proposal.key, str(int(data.url_ndb_proposal_revision.key.id()) + 1),
soc_urls.UrlNames.PROPOSAL_REVISION_REVIEW)
return {
'abstract': proposal.abstract,
'additional_info': proposal.additional_info,
'content': data.url_ndb_proposal_revision.content,
'org_name': proposal.organization.get().name,
'page_name': _PROPOSAL_REVISION_REVIEW_PAGE_NAME,
'student_name': proposal.key.parent().get().public_name,
'title': proposal.title,
'prev_revision_url' : prev_revision_url,
'next_revision_url' : next_revision_url,
}
PROPOSAL_REVISION_REVIEW = (
ProposalRevisionReviewPage(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames,
'summerofcode/proposal/proposal_revision_review.html'))
class ProposalReviewAsStudentPage(base.RequestHandler):
"""View to review a proposal as a student."""
access_checker = access.IS_URL_USER_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names, template_path):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
template_path: The path of the template to be used.
"""
super(ProposalReviewAsStudentPage, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
self.template_path = template_path
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/review/student/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_REVIEW_AS_STUDENT),
]
def templatePath(self):
"""See base.RequestHandler.templatePath for specification."""
return self.template_path
def context(self, data, check, mutator):
"""See base.RequestHandler.context for specification."""
# proposal is editable when the student sign-up is open or when
# organization members agree on subsequent modifications
is_editable = proposal_logic.canStudentUpdateProposal(
data.url_ndb_proposal, data.program.timeline)
top_msg = top_message.proposalReviewAsStudentTopMessage(data)
edit_url = None if not is_editable else links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_EDIT)
revisions_url = links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_REVISIONS_LIST)
if (data.url_ndb_proposal.visibility == proposal_model.Visibility.PUBLIC or
data.url_ndb_proposal.visibility == proposal_model.Visibility.STUDENT):
public_url = links.ABSOLUTE_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_PUBLIC)
else:
public_url = None
proposal_is_public = (
data.url_ndb_proposal.visibility == proposal_model.Visibility.PUBLIC)
public_comments = proposal_logic.getComments(
data.url_ndb_proposal.key, exclude_private=True)
public_comments.sort(key=lambda item: item.created)
comment_form = _commentFormToReviewAsStudent(data=data.POST)
toggle_buttons = _toggleButtonsForStudent(data, self.url_names)
manage_actions = sidebar_actions.SidebarActions(
data, _MANAGE_ACTIONS_TITLE, toggle_buttons) if toggle_buttons else None
return {
'abstract': data.url_ndb_proposal.abstract,
'additional_info': data.url_ndb_proposal.additional_info,
'comment_form': comment_form,
'content': data.url_ndb_proposal.content,
'manage_actions': manage_actions,
'org_name': data.url_ndb_proposal.organization.get().name,
'page_name': _PROPOSAL_REVIEW_PAGE_NAME,
'public_comments': public_comments,
'public_url': public_url,
'proposal_is_public': proposal_is_public,
'student_name': data.url_ndb_proposal.key.parent().get().public_name,
'title': data.url_ndb_proposal.title,
'edit_url': edit_url,
'revisions_url': revisions_url,
'top_message': top_msg,
}
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
comment_form = _commentFormToReviewAsStudent(data=data.POST)
if comment_form.is_valid():
content = comment_form.cleaned_data['content']
profiles_to_notify = (
soc_profile_logic.getProfilesForNewProposalCommentNotification(
data.url_ndb_proposal.organization, data.ndb_profile.key))
proposal_logic.createComment(
data.url_ndb_proposal, content, data.ndb_profile, False,
profiles_to_notify=profiles_to_notify, program=data.program,
organization=data.url_ndb_proposal.organization.get(),
site=data.site, url_names=self.url_names)
url = links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_REVIEW_AS_STUDENT) + '?verified=True'
return http.HttpResponseRedirect(url)
else:
# TODO(nathaniel): problematic self-use.
return self.get(data, check, mutator)
PROPOSAL_REVIEW_AS_STUDENT_PAGE = (
ProposalReviewAsStudentPage(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames, 'summerofcode/proposal/proposal_review.html'))
class IsMentorForUrlProposalOrganizationAccessChecker(access.AccessChecker):
"""AccessChecker that ensures that the currently logged in user is
a mentor for the organization corresponding the proposal which is defined
in the URL.
"""
def checkAccess(self, data, check):
"""See AccessChecker.checkAccess for specification."""
if data.url_ndb_proposal.organization not in data.ndb_profile.mentor_for:
raise exception.Forbidden(message=_MESSAGE_CURRENT_USER_NOT_MENTOR)
PROPOSAL_REVIEW_AS_ORG_MEMBER_ACCESS_CHECKER = access.DisjunctionAccessChecker([
access.ConjuctionAccessChecker([
access.HAS_PROFILE_ACCESS_CHECKER,
IsMentorForUrlProposalOrganizationAccessChecker()]),
access.PROGRAM_ADMINISTRATOR_ACCESS_CHECKER,
])
class ProposalReviewAsOrgMemberPage(base.RequestHandler):
"""View to review a proposal as an organization member."""
access_checker = PROPOSAL_REVIEW_AS_ORG_MEMBER_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names, template_path):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
template_path: The path of the template to be used.
"""
super(ProposalReviewAsOrgMemberPage, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
self.template_path = template_path
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/review/org/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_REVIEW_AS_ORG_MEMBER),
]
def templatePath(self):
"""See base.RequestHandler.templatePath for specification."""
return self.template_path
def context(self, data, check, mutator):
"""See base.RequestHandler.context for specification."""
mentors = ', '.join(
mentor_key.get().public_name
for mentor_key in data.url_ndb_proposal.mentors)
possible_mentors = ', '.join(
mentor_key.get().public_name
for mentor_key in data.url_ndb_proposal.possible_mentors)
# segregate the comments by their visibility
public_comments = []
private_comments = []
for comment in proposal_logic.getComments(data.url_ndb_proposal.key):
if comment.is_private:
private_comments.append(comment)
else:
public_comments.append(comment)
# sort comments by creation date
public_comments.sort(key=lambda item: item.created)
private_comments.sort(key=lambda item: item.created)
comment_form = _commentFormToReviewAsOrgMember(data=data.POST)
# TODO(daniel): it should be possible to disable/hide scoring
scoring_visible = True
user_score = None
other_scores = {}
for score in data.url_ndb_proposal.scores:
if score.author == data.ndb_profile.key:
user_score = score
else:
if score.value not in other_scores:
other_scores[score.value] = []
other_scores[score.value].append(score.author.get().public_name)
# Order the scores for consistency.
other_scores = collections.OrderedDict(sorted(other_scores.items()))
average = data.url_ndb_proposal.average_score
score = {
'average': float("%.2f" % average) if average else 0,
'number': data.url_ndb_proposal.count_scores,
'total': data.url_ndb_proposal.total_score,
'user_score': user_score.value if user_score else 0,
'other_scores': other_scores,
'max_score': data.url_ndb_proposal.organization.get().max_score,
'score_action': links.SOC_LINKER.userId(
data.url_ndb_proposal.key.parent(), data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_SCORE)
}
is_admin = data.url_ndb_proposal.organization in data.ndb_profile.admin_for
is_mentor = (
data.url_ndb_proposal.organization in data.ndb_profile.mentor_for)
toggle_buttons = (
_toggleButtonsForMentor(data, self.url_names) if is_mentor else [])
if is_admin or data.is_host or data.is_developer:
toggle_buttons.extend(_toggleButtonsForAdmin(data, self.url_names))
all_mentors = profile_logic.queryAllMentorsForOrg(
data.url_ndb_proposal.organization).fetch(1000, keys_only=True)
assign_mentor_url = links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_ASSIGN_MENTORS)
assign_mentor_field = assign_mentor.AssignMentorFields(
data, data.url_ndb_proposal.mentors, assign_mentor_url,
all_mentors=all_mentors, allow_multiple=True)
else:
assign_mentor_field = None
if ((data.is_host or data.is_developer) and
data.url_ndb_proposal.status != proposal_model.Status.ACCEPTED):
set_status_url = links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_ADMIN_ACCEPT)
set_status_field = SetStatusField(data, set_status_url)
else:
set_status_field = None
revisions_url = links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_REVISIONS_LIST)
manage_actions = sidebar_actions.SidebarActions(
data, _MANAGE_ACTIONS_TITLE, toggle_buttons,
assign_mentor_field=assign_mentor_field,
set_status_field=set_status_field)
duplicate_data = (
DuplicateData(data)
if (data.url_ndb_profile.student_data.has_duplicates and
((is_admin and data.program.duplicates_visible)
or data.is_host or data.is_developer))
else None)
return {
'abstract': data.url_ndb_proposal.abstract,
'additional_info': data.url_ndb_proposal.additional_info,
'admin_only_data_visible': is_admin,
'comment_form': comment_form,
'content': data.url_ndb_proposal.content,
'duplicate_data': duplicate_data,
'is_ignored': data.url_ndb_proposal.is_ignored,
'manage_actions': manage_actions,
'mentor_only_data_visible': True,
'mentors': mentors,
'org_name': data.url_ndb_proposal.organization.get().name,
'page_name': _PROPOSAL_REVIEW_PAGE_NAME,
'possible_mentors': possible_mentors,
'private_comments': private_comments,
'proposal_status': data.url_ndb_proposal.status,
'public_comments': public_comments,
'revisions_url': revisions_url,
'score': score,
'scoring_visible': scoring_visible,
'student_name': data.url_ndb_proposal.key.parent().get().public_name,
'student_email': data.url_ndb_profile.contact.email,
'title': data.url_ndb_proposal.title,
}
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
comment_form = _commentFormToReviewAsOrgMember(data=data.POST)
if comment_form.is_valid():
content = comment_form.cleaned_data['content']
is_private = comment_form.cleaned_data['is_private']
profiles_to_notify = (
soc_profile_logic.getProfilesForNewProposalCommentNotification(
data.url_ndb_proposal.organization, data.ndb_profile.key))
student_to_notify = (
data.url_ndb_proposal.key.parent().get() if not is_private else None)
proposal_logic.createComment(
data.url_ndb_proposal, content, data.ndb_profile,
is_private, profiles_to_notify=profiles_to_notify,
student_to_notify=student_to_notify,
program=data.program,
organization=data.url_ndb_proposal.organization.get(),
site=data.site, url_names=self.url_names)
url = links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_REVIEW_AS_ORG_MEMBER) + '?verified=True'
return http.HttpResponseRedirect(url)
else:
# TODO(nathaniel): problematic self-use.
return self.get(data, check, mutator)
PROPOSAL_REVIEW_AS_ORG_MEMBER_PAGE = (
ProposalReviewAsOrgMemberPage(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames, 'summerofcode/proposal/proposal_review.html'))
class IsAdminForUrlProposalOrganizationAccessChecker(access.AccessChecker):
"""AccessChecker that ensures that the currently logged in user is
an administrator for the organization corresponding the proposal which
is defined in the URL.
"""
def checkAccess(self, data, check):
"""See AccessChecker.checkAccess for specification."""
if data.url_ndb_proposal.organization not in data.ndb_profile.admin_for:
raise exception.Forbidden(message=_MESSAGE_CURRENT_USER_NOT_MENTOR)
# Universal access checker used by all handlers which are accessible only
# by organization administrators and program administrators.
MANAGE_PROPOSAL_ACCESS_CHECKER = access.DisjunctionAccessChecker([
access.ConjuctionAccessChecker([
access.HAS_PROFILE_ACCESS_CHECKER,
IsAdminForUrlProposalOrganizationAccessChecker()]),
access.PROGRAM_ADMINISTRATOR_ACCESS_CHECKER])
class ProposalAcceptAsProjectHandlerTest(base.RequestHandler):
"""Handler to mark a proposal that it should be accepted as a project."""
access_checker = MANAGE_PROPOSAL_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalAcceptAsProjectHandlerTest, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/accept_as_project/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_ACCEPT_AS_PROJECT),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.updateProposal(
data.url_ndb_proposal.key, {'accept_as_project': True})
return http.HttpResponse() if result else http.HttpResponseBadRequest()
PROPOSAL_ACCEPT_AS_PROJECT_HANDLER = ProposalAcceptAsProjectHandlerTest(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalRejectAsProjectHandler(base.RequestHandler):
"""Handler to mark a proposal that it should not be accepted as a project."""
access_checker = MANAGE_PROPOSAL_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalRejectAsProjectHandler, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/reject_as_project/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_REJECT_AS_PROJECT),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.updateProposal(
data.url_ndb_proposal.key, {'accept_as_project': False})
return http.HttpResponse() if result else http.HttpResponseBadRequest()
PROPOSAL_REJECT_AS_PROJECT_HANDLER = ProposalRejectAsProjectHandler(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ScoringEnabledAccessChecker(access.AccessChecker):
"""AccessChecker that ensures that scoring is enabled for the organization
corresponding the proposal which is defined in the URL.
"""
def checkAccess(self, data, check):
"""See AccessChecker.checkAccess for specification."""
# TODO(daniel): implement this
pass
PROPOSAL_SCORE_ACCESS_CHECKER = access.ConjuctionAccessChecker([
access.HAS_PROFILE_ACCESS_CHECKER,
IsMentorForUrlProposalOrganizationAccessChecker(),
ScoringEnabledAccessChecker()])
class ProposalScore(base.RequestHandler):
"""Handler to score a proposal."""
access_checker = PROPOSAL_SCORE_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalScore, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/score/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_SCORE),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
submitted_value = data.POST.get('value', '')
value = int(submitted_value) if submitted_value.isdigit() else None
# NOTE: this is required because 0 is sent in case of removing a score
if value == 0:
value = None
result = proposal_logic.setScoreForProfile(
data.url_ndb_proposal.key, data.ndb_profile.key, value,
data.url_ndb_proposal.organization.get())
if not result:
raise exception.BadRequest(message=result.extra)
else:
return http.HttpResponse()
PROPOSAL_SCORE = ProposalScore(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class UrlProposalNotIgnoredAccessChecker(access.AccessChecker):
"""AccessChecker that ensures that the proposal specified in the URL
is not ignored.
"""
def checkAccess(self, data, check):
"""See AccessChecker.checkAccess for specification."""
if data.url_ndb_proposal.status == proposal_model.Status.IGNORED:
raise exception.Forbidden(message=_MESSAGE_PROPOSAL_IGNORED)
MENTOR_ACTION_ACCESS_CHECKER = access.ConjuctionAccessChecker([
access.HAS_PROFILE_ACCESS_CHECKER,
IsMentorForUrlProposalOrganizationAccessChecker(),
UrlProposalNotIgnoredAccessChecker()])
class ProposalPossibleMentorAdd(base.RequestHandler):
"""Handler to add the currently logged in profile to the list of
possible mentors for the proposal.
"""
access_checker = MENTOR_ACTION_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalPossibleMentorAdd, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/possible_mentor/add/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_POSSIBLE_MENTOR_ADD),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
proposal_logic.addPossibleMentor(
data.url_ndb_proposal.key, data.ndb_profile.key)
return http.HttpResponse()
PROPOSAL_POSSIBLE_MENTOR_ADD = ProposalPossibleMentorAdd(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalPossibleMentorRemove(base.RequestHandler):
"""Handler to remove the currently logged in profile from the list of
possible mentors for the proposal.
"""
access_checker = MENTOR_ACTION_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalPossibleMentorRemove, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/possible_mentor/remove/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_POSSIBLE_MENTOR_REMOVE),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
proposal_logic.removePossibleMentor(
data.url_ndb_proposal.key, data.ndb_profile.key)
return http.HttpResponse()
PROPOSAL_POSSIBLE_MENTOR_REMOVE = ProposalPossibleMentorRemove(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalIgnore(base.RequestHandler):
"""Handler to mark the proposal as ignored."""
access_checker = MANAGE_PROPOSAL_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalIgnore, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/ignore/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_IGNORE),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.updateProposal(
data.url_ndb_proposal.key,
{proposal_model.Proposal.is_ignored._name: True})
if not result:
raise exception.BadRequest(message=result.extra)
else:
return http.HttpResponse()
PROPOSAL_IGNORE = ProposalIgnore(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalUnignore(base.RequestHandler):
"""Handler to mark the proposal as non-ignored."""
access_checker = MANAGE_PROPOSAL_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalUnignore, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/unignore/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_UNIGNORE),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.updateProposal(
data.url_ndb_proposal.key,
{proposal_model.Proposal.is_ignored._name: False})
if not result:
raise exception.BadRequest(message=result.extra)
else:
return http.HttpResponse()
PROPOSAL_UNIGNORE = ProposalUnignore(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalWithdraw(base.RequestHandler):
"""Handler to withdraw the proposal."""
access_checker = access.IS_URL_USER_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalWithdraw, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/withdraw/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_WITHDRAW),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.withdrawProposal(data.url_ndb_proposal.key)
if not result:
raise exception.BadRequest(message=result.extra)
else:
return http.HttpResponse()
PROPOSAL_WITHDRAW = ProposalWithdraw(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalResubmit(base.RequestHandler):
"""Handler to resubmit the previously withdrawn proposal."""
access_checker = access.IS_URL_USER_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalResubmit, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/resubmit/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_RESUBMIT),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.resubmitProposal(
data.url_ndb_proposal.key, data.program)
if not result:
raise exception.BadRequest(message=result.extra)
else:
return http.HttpResponse()
PROPOSAL_RESUBMIT = ProposalResubmit(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalAssignMentors(base.RequestHandler):
"""Handler to assign mentors for the specified proposal."""
access_checker = MANAGE_PROPOSAL_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalAssignMentors, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/assign_mentors/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_ASSIGN_MENTORS),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
mentor_keys = set(
ndb.Key(urlsafe=urlsafe)
for urlsafe in data.POST.getlist('assign_mentor') if urlsafe)
mentors = ndb.get_multi(mentor_keys)
result = proposal_logic.assignMentors(
data.url_ndb_proposal.key, mentors)
if not result:
raise exception.BadRequest(
message=_MESSAGE_USERS_NOT_MENTORS %
', '.join(mentor.public_name for mentor in result.extra))
else:
url = links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_REVIEW_AS_ORG_MEMBER) + '?verified=True'
return http.HttpResponseRedirect(url)
PROPOSAL_ASSIGN_MENTORS = ProposalAssignMentors(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalEditablePostDeadline(base.RequestHandler):
"""Handler to mark the proposal as editable after the proposal submission
deadline passes.
"""
access_checker = MENTOR_ACTION_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalEditablePostDeadline, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/editable_post_deadline/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_EDITABLE_POST_DEADLINE),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.updateProposal(
data.url_ndb_proposal.key, {'is_editable_post_deadline': True})
return http.HttpResponse() if result else http.HttpResponseBadRequest()
PROPOSAL_EDITABLE_POST_DEADLINE = ProposalEditablePostDeadline(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalUneditablePostDeadline(base.RequestHandler):
"""Handler to mark the proposal as non editable after the proposal submission
deadline passes.
"""
access_checker = MENTOR_ACTION_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalUneditablePostDeadline, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/uneditable_post_deadline/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_UNEDITABLE_POST_DEADLINE),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.updateProposal(
data.url_ndb_proposal.key, {'is_editable_post_deadline': False})
return http.HttpResponse() if result else http.HttpResponseBadRequest()
PROPOSAL_UNEDITABLE_POST_DEADLINE = ProposalUneditablePostDeadline(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)
class ProposalAdminAccept(base.RequestHandler):
"""Handler to accept the proposal by a program administrator."""
access_checker = access.PROGRAM_ADMINISTRATOR_ACCESS_CHECKER
def __init__(self, initializer, linker, renderer, error_handler,
url_pattern_constructor, url_names):
"""Initializes a new instance of the request handler for the specified
parameters.
Args:
initializer: Implementation of initialize.Initializer interface.
linker: Instance of links.Linker class.
renderer: Implementation of render.Renderer interface.
error_handler: Implementation of error.ErrorHandler interface.
url_pattern_constructor:
Implementation of url_patterns.UrlPatternConstructor.
url_names: Instance of url_names.UrlNames.
"""
super(ProposalAdminAccept, self).__init__(
initializer, linker, renderer, error_handler)
self.url_pattern_constructor = url_pattern_constructor
self.url_names = url_names
def djangoURLPatterns(self):
"""See base.RequestHandler.djangoURLPatterns for specification."""
return [
self.url_pattern_constructor.construct(
r'proposal/admin/accept/%s$' % url_patterns.USER_ID,
self, name=self.url_names.PROPOSAL_ADMIN_ACCEPT),
]
def post(self, data, check, mutator):
"""See base.RequestHandler.post for specification."""
result = proposal_logic.acceptProposal(data.url_ndb_proposal.key)
if not result:
raise exception.BadRequest(
message=_MESSAGE_CANNOT_ACCEPT_PROPOSAL)
else:
url = links.SOC_LINKER.userId(
data.url_ndb_profile.key, data.url_ndb_proposal.key.id(),
self.url_names.PROPOSAL_REVIEW_AS_ORG_MEMBER) + '?verified=True'
return http.HttpResponseRedirect(url)
PROPOSAL_ADMIN_ACCEPT = ProposalAdminAccept(
gsoc_base._GSOC_INITIALIZER, links.SOC_LINKER, render.SOC_RENDERER,
error.SOC_ERROR_HANDLER, soc_url_patterns.SOC_URL_PATTERN_CONSTRUCTOR,
soc_urls.UrlNames)