blob: 2a3ab6ad456b1663efc2dfeb1dc116c032f40dd5 [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."""
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 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.views.helper import urls as soc_urls
_PROPOSAL_PUBLIC_PAGE_NAME = translation.ugettext('Proposal')
_PROPOSAL_REVIEW_PAGE_NAME = translation.ugettext('Review Proposal')
_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_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_WISH_TO_MENTOR_TITLE = translation.ugettext('Wish to mentor')
_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
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.
"""
return [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=data.url_ndb_proposal.status == proposal_model.Status.WITHDRAWN,
labels={'checked': 'Yes', 'unchecked': 'No'},
help_text=_BUTTON_WITHDRAW_HELP_TEXT)]
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.
"""
return [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)]
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
class ManageActions(template.Template):
"""Template to render the left side user actions."""
def __init__(self, data, toggle_buttons, assign_mentor_field=None):
"""Initializes a new instance of this class.
Args:
data: request_data.RequestData for the current request.
toggle_buttons: List of toggle buttons to use for the actions.
assign_mentor_field: Optional field to assign mentors for the proposal.
"""
super(ManageActions, self).__init__(data)
self.toggle_buttons = toggle_buttons
self.assign_mentor_field = assign_mentor_field
def templatePath(self):
"""See template.Template.templatPath for specification"""
return 'modules/gsoc/proposal/_user_action.html'
def context(self):
"""See template.Template.context for specification."""
return {
'assign_mentor_field': self.assign_mentor_field,
'title': _MANAGE_ACTIONS_TITLE,
'toggle_buttons': self.toggle_buttons,
}
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 ProposalPublicPage(base.RequestHandler):
"""View to display proposal publicly."""
access_checker = access.DisjunctionAccessChecker([
IsUrlProposalPubliclyVisibleAccessChecker(),
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,
'public_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 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 = data.timeline.studentSignup() or (
data.url_ndb_proposal.is_editable_post_deadline and
data.timeline.afterStudentSignupEnd())
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)
if data.url_ndb_proposal.visibility == proposal_model.Visibility.PUBLIC:
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
public_comments = proposal_logic.getComments(
data.url_ndb_proposal.key, exclude_private=True)
comment_form = _commentFormToReviewAsStudent(data=data.POST)
toggle_buttons = _toggleButtonsForStudent(data, self.url_names)
manage_actions = ManageActions(data, toggle_buttons)
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,
'student_name': data.url_ndb_proposal.key.parent().get().public_name,
'title': data.url_ndb_proposal.title,
'edit_url': edit_url,
}
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.ConjuctionAccessChecker([
access.HAS_PROFILE_ACCESS_CHECKER,
IsMentorForUrlProposalOrganizationAccessChecker()])
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 = proposal_logic.getScoreForProfile(
data.url_ndb_proposal, data.ndb_profile.key)
score = {
'average': data.url_ndb_proposal.average_score or 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,
'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:
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
manage_actions = ManageActions(data, toggle_buttons,
assign_mentor_field=assign_mentor_field)
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,
'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,
'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)
PROPOSAL_POSSIBLE_MENTOR_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 = PROPOSAL_POSSIBLE_MENTOR_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 = PROPOSAL_POSSIBLE_MENTOR_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)