| # 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 |
| |
| 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.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 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') |
| |
| _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.key) |
| 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 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) |
| |
| 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, |
| '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 |
| |
| 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, |
| '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) |