blob: 930e8b333b4e78ab8c1a559234c61462503ed2ee [file] [log] [blame]
#!/usr/bin/env python2.5
#
# Copyright 2011 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 for the GSoC proposal page.
"""
from google.appengine.ext import db
from django.core.urlresolvers import resolve
from django.core.urlresolvers import reverse
from django import forms as django_forms
from django.utils.translation import ugettext
from soc.logic import cleaning
from soc.logic.exceptions import BadRequest
from soc.views.helper import url as url_helper
from soc.views.helper.access_checker import isSet
from soc.views.template import Template
from soc.views.toggle_button import ToggleButtonTemplate
from soc.tasks import mailer
from soc.modules.gsoc.logic import profile as profile_logic
from soc.modules.gsoc.logic.helper import notifications
from soc.modules.gsoc.models.comment import GSoCComment
from soc.modules.gsoc.models.proposal_duplicates import GSoCProposalDuplicate
from soc.modules.gsoc.models.profile import GSoCProfile
from soc.modules.gsoc.models.score import GSoCScore
from soc.modules.gsoc.views import assign_mentor
from soc.modules.gsoc.views.base import RequestHandler
from soc.modules.gsoc.views.forms import GSoCModelForm
from soc.modules.gsoc.views.helper import url_patterns
from soc.modules.gsoc.views.helper.url_patterns import url
class CommentForm(GSoCModelForm):
"""Django form for the comment.
"""
class Meta:
model = GSoCComment
#css_prefix = 'gsoc_comment'
fields = ['content']
def clean_content(self):
field_name = 'content'
wrapped_clean_html_content = cleaning.clean_html_content(field_name)
content = wrapped_clean_html_content(self)
if content:
return content
else:
raise django_forms.ValidationError(
ugettext('Comment content cannot be empty.'), code='invalid')
def templatePath(self):
return 'v2/modules/gsoc/proposal/_comment_form.html'
class PrivateCommentForm(CommentForm):
"""Django form for the comment.
"""
class Meta:
model = GSoCComment
fields = CommentForm.Meta.fields + ['is_private']
class Duplicate(Template):
"""Template for showing a duplicates to the org admin.
"""
def __init__(self, data, duplicate):
"""Instantiates the template for rendering duplicates for a single
proposal.
Args:
data: RequestData object
duplicate: GSoCProposalDuplicate entity to render.
"""
self.duplicate = duplicate
super(Duplicate, self).__init__(data)
def context(self):
"""The context for this template used in render().
"""
r = self.data.redirect
orgs = []
for org in db.get(self.duplicate.orgs):
q = GSoCProfile.all()
q.filter('org_admin_for', org)
q.filter('status', 'active')
admins = q.fetch(1000)
data = {'name': org.name,
'link': r.organization(org).urlOf('gsoc_org_home'),
'admins': admins}
orgs.append(data)
context = {'orgs': orgs}
return context
def templatePath(self):
return 'v2/modules/gsoc/duplicates/proposal_duplicate_review.html'
class UserActions(Template):
"""Template to render the left side user actions.
"""
DEF_ACCEPT_PROPOSAL_HELP = ugettext(
'Choosing Yes will mark this proposal as accepted. The proposal is '
'accepted when Yes is displayed in bright orange.')
DEF_IGNORE_PROPOSAL_HELP = 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.')
DEF_IGNORE_PROPOSAL_NOTE = ugettext(
'Please refresh this page after setting this preference.')
DEF_PROPOSAL_MODIFICATION_HELP = ugettext(
'Choosing Enabled allows the student to edit this proposal. The '
'student can edit the proposal when Enabled is displayed in bright '
'orange.')
DEF_PUBLICLY_VISIBLE_HELP = ugettext(
'Choosing Yes will make this proposal publicly visible. The proposal '
'will be visible to even those who do not have a user account on this '
'site. The proposal is publicly visible when Yes is displayed in '
'bright orange')
DEF_WISH_TO_MENTOR_HELP = 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.')
DEF_WITHDRAW_PROPOSAL_HELP = 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.')
def __init__(self, data, user_role):
super(UserActions, self).__init__(data)
self.user_role = user_role
self.toggle_buttons = []
def _mentorContext(self):
"""Construct the context needed for mentor actions.
"""
r = self.data.redirect.review()
wish_to_mentor = ToggleButtonTemplate(
self.data, 'on_off', 'Wish to Mentor', 'wish-to-mentor',
r.urlOf('gsoc_proposal_wish_to_mentor'),
checked=self.data.isPossibleMentorForProposal(),
help_text=self.DEF_WISH_TO_MENTOR_HELP,
labels = {
'checked': 'Yes',
'unchecked': 'No'})
self.toggle_buttons.append(wish_to_mentor)
if self.data.timeline.afterStudentSignupEnd():
proposal_modification_button = ToggleButtonTemplate(
self.data, 'long', 'Proposal Modifications', 'proposal-modification',
r.urlOf('gsoc_proposal_modification'),
checked=self.data.proposal.is_editable_post_deadline,
help_text=self.DEF_PROPOSAL_MODIFICATION_HELP,
labels = {
'checked': 'Enabled',
'unchecked': 'Disabled'})
self.toggle_buttons.append(proposal_modification_button)
return {}
def _orgAdminContext(self):
"""Construct the context needed for org admin actions.
"""
context = {}
r = self.data.redirect.review()
ignore_button_checked = False
if self.data.proposal.status == 'ignored':
ignore_button_checked = True
if self.data.proposal.status in ['pending', 'withdrawn', 'ignored']:
ignore_proposal = ToggleButtonTemplate(
self.data, 'on_off', 'Ignore Proposal', 'proposal-ignore',
r.urlOf('gsoc_proposal_ignore'),
checked=ignore_button_checked,
help_text=self.DEF_IGNORE_PROPOSAL_HELP,
note=self.DEF_IGNORE_PROPOSAL_NOTE,
labels={
'checked': 'Yes',
'unchecked': 'No'})
self.toggle_buttons.append(ignore_proposal)
if not self.proposal_ignored:
accept_proposal = ToggleButtonTemplate(
self.data, 'on_off', 'Accept proposal', 'accept-proposal',
r.urlOf('gsoc_proposal_accept'),
checked=self.data.proposal.accept_as_project,
help_text=self.DEF_ACCEPT_PROPOSAL_HELP,
labels = {
'checked': 'Yes',
'unchecked': 'No',})
self.toggle_buttons.append(accept_proposal)
r = self.data.redirect
possible_mentors_keys = self.data.proposal.possible_mentors
all_mentors_keys = profile_logic.queryAllMentorsKeysForOrg(
self.data.proposal_org) if self.data.proposal_org.list_all_mentors \
else []
current_mentors = []
if self.data.proposal.mentor:
current_mentors.append(self.data.proposal.mentor.key())
context['assign_mentor'] = assign_mentor.AssignMentorFields(
self.data, current_mentors,
r.review().urlOf('gsoc_proposal_assign_mentor'),
all_mentors_keys, possible_mentors_keys)
return context
def _proposerContext(self):
"""Construct the context needed for proposer actions.
"""
r = self.data.redirect.review()
publicly_visible = ToggleButtonTemplate(
self.data, 'on_off', 'Publicly Visible', 'publicly-visible',
r.urlOf('gsoc_proposal_publicly_visible'),
checked=self.data.proposal.is_publicly_visible,
help_text=self.DEF_PUBLICLY_VISIBLE_HELP,
labels = {
'checked': 'Yes',
'unchecked': 'No',})
self.toggle_buttons.append(publicly_visible)
if self.data.proposal.status in ['pending', 'withdrawn']:
if self.data.proposal.status == 'withdrawn':
checked=True
elif self.data.proposal.status == 'pending':
checked=False
withdraw_proposal = ToggleButtonTemplate(
self.data, 'on_off', 'Withdraw Proposal', 'withdraw-proposal',
r.urlOf('gsoc_proposal_withdraw'), checked=checked,
help_text=self.DEF_WITHDRAW_PROPOSAL_HELP,
labels = {
'checked': 'Yes',
'unchecked': 'No',})
self.toggle_buttons.append(withdraw_proposal)
return {}
def context(self):
assert isSet(self.data.proposal)
context = {
'title': 'Proposal Actions',
}
self.proposal_ignored = self.data.proposal.status == 'ignored'
if self.user_role == 'mentor' and not self.proposal_ignored:
context.update(self._mentorContext())
if self.user_role == 'org_admin':
context.update(self._orgAdminContext())
# org admin is a mentor by default so add that context and buttons
# as well.
if not self.proposal_ignored:
context.update(self._mentorContext())
if self.user_role == 'proposer':
context.update(self._proposerContext())
context['toggle_buttons'] = self.toggle_buttons
return context
def templatePath(self):
return "v2/modules/gsoc/proposal/_user_action.html"
class ReviewProposal(RequestHandler):
"""View for the Propsal Review page.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/review/%s$' % url_patterns.REVIEW,
self, name='review_gsoc_proposal'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
self.check.canAccessProposalEntity()
self.mutator.commentVisible()
def templatePath(self):
return 'v2/modules/gsoc/proposal/review.html'
def getScores(self):
"""Gets all the scores for the proposal.
"""
assert isSet(self.data.private_comments_visible)
assert isSet(self.data.proposal_org)
assert isSet(self.data.proposal)
if not self.data.private_comments_visible:
return None
total = 0
number = 0
user_score = 0
query = db.Query(GSoCScore).ancestor(self.data.proposal)
for score in query:
total += score.value
number += 1
author_key = GSoCScore.author.get_value_for_datastore(score)
if author_key == self.data.profile.key():
user_score = score.value
return {
'average': total / number if number else 0,
'number': number,
'total': total,
'user_score': user_score,
}
def getComments(self, limit=1000):
"""Gets all the comments for the proposal visible by the current user.
"""
assert isSet(self.data.private_comments_visible)
assert isSet(self.data.proposal)
public_comments = []
private_comments = []
query = db.Query(GSoCComment).ancestor(self.data.proposal)
query.order('created')
all_comments = query.fetch(limit=limit)
for comment in all_comments:
if not comment.is_private:
public_comments.append(comment)
elif self.data.private_comments_visible:
private_comments.append(comment)
return public_comments, private_comments
def sanitizePossibleMentors(self, possible_mentors):
"""Removes possible mentors that are no longer mentors
"""
changed = False
result = []
for mentor in possible_mentors:
if self.data.proposal_org.key() in mentor.mentor_for:
result.append(mentor)
continue
changed = True
self.data.proposal.possible_mentors.remove(mentor.key())
if changed:
self.data.proposal.put()
return result
def context(self):
assert isSet(self.data.public_comments_visible)
assert isSet(self.data.private_comments_visible)
assert isSet(self.data.url_profile)
assert isSet(self.data.url_user)
assert isSet(self.data.proposal)
context = {}
user_role = None
scores = self.getScores()
# TODO: check if the scoring is not disabled
score_action = reverse('score_gsoc_proposal', kwargs=self.data.kwargs)
# get all the comments for the the proposal
public_comments, private_comments = self.getComments()
# TODO: check if it is possible to post a comment
comment_action = reverse('comment_gsoc_proposal', kwargs=self.data.kwargs)
if self.data.private_comments_visible:
form = PrivateCommentForm(self.data.POST or None)
if self.data.orgAdminFor(self.data.proposal.org):
user_role = 'org_admin'
else:
user_role = 'mentor'
else:
form = CommentForm(self.data.POST or None)
comment_box = {
'action': comment_action,
'form': form,
}
# to keep the blocks as simple as possible, the if branches have
# been broken down into several if blocks
user_is_proposer = self.data.user and \
(self.data.user.key() == self.data.url_user.key())
if user_is_proposer:
user_role = 'proposer'
# we will check if the student is allowed to modify the proposal
# after the student proposal deadline
is_editable = self.data.timeline.afterStudentSignupEnd() and \
self.data.proposal.is_editable_post_deadline
if self.data.timeline.studentSignup() or is_editable:
context['update_link'] = self.data.redirect.id().urlOf(
'update_gsoc_proposal')
possible_mentors = db.get(self.data.proposal.possible_mentors)
possible_mentors = self.sanitizePossibleMentors(possible_mentors)
possible_mentors_names = ', '.join([m.name() for m in possible_mentors])
scoring_visible = self.data.private_comments_visible and (
not self.data.proposal_org.scoring_disabled)
if self.data.orgAdminFor(self.data.proposal_org):
scoring_visible = True
duplicate = None
if self.data.program.duplicates_visible and self.data.orgAdminFor(
self.data.proposal_org):
q = GSoCProposalDuplicate.all()
q.filter('duplicates', self.data.proposal)
q.filter('is_duplicate', True)
dup_entity = q.get()
duplicate = Duplicate(self.data, dup_entity) if dup_entity else None
additional_info = self.data.proposal.additional_info
if user_role:
context['user_actions'] = UserActions(self.data, user_role)
context.update({
'additional_info': url_helper.trim_url_to(additional_info, 50),
'additional_info_link': additional_info,
'comment_box': comment_box,
'duplicate': duplicate,
'max_score': self.data.proposal_org.max_score,
'mentor': self.data.proposal.mentor,
'page_name': self.data.proposal.title,
'possible_mentors': possible_mentors_names,
'private_comments': private_comments,
'private_comments_visible': self.data.private_comments_visible,
'proposal': self.data.proposal,
'public_comments': public_comments,
'public_comments_visible': self.data.public_comments_visible,
'score_action': score_action,
'scores': scores,
'scoring_visible': scoring_visible,
'student_email': self.data.url_profile.email,
'student_name': self.data.url_profile.name(),
'proposal_ignored': self.data.proposal.status == 'ignored',
'user_role': user_role,
})
return context
class PostComment(RequestHandler):
"""View which handles publishing comments.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/comment/%s$' % url_patterns.REVIEW,
self, name='comment_gsoc_proposal'),
]
def checkAccess(self):
self.check.isProgramVisible()
self.check.isProfileActive()
self.mutator.proposalFromKwargs()
self.mutator.commentVisible()
assert isSet(self.data.proposer)
assert isSet(self.data.proposal_org)
# check if the comment is given by the author of the proposal
if self.data.proposer.key() == self.data.profile.key():
self.data.public_only = True
return
self.data.public_only = False
self.check.isMentorForOrganization(self.data.proposal_org)
def createCommentFromForm(self):
"""Creates a new comment based on the data inserted in the form.
Returns:
a newly created comment entity or None
"""
assert isSet(self.data.public_only)
assert isSet(self.data.proposal)
if self.data.public_only:
comment_form = CommentForm(self.data.request.POST)
else:
# this form contains checkbox for indicating private/public comments
comment_form = PrivateCommentForm(self.data.request.POST)
if not comment_form.is_valid():
return None
if self.data.public_only:
comment_form.cleaned_data['is_private'] = False
comment_form.cleaned_data['author'] = self.data.profile
q = GSoCProfile.all().filter('mentor_for', self.data.proposal.org)
q = q.filter('status', 'active')
if comment_form.cleaned_data.get('is_private'):
q.filter('notify_private_comments', True)
else:
q.filter('notify_public_comments', True)
mentors = q.fetch(1000)
to_emails = [i.email for i in mentors \
if i.key() != self.data.profile.key()]
def create_comment_txn():
comment = comment_form.create(commit=True, parent=self.data.proposal)
context = notifications.newReviewContext(self.data, comment, to_emails)
sub_txn = mailer.getSpawnMailTaskTxn(context, parent=comment)
sub_txn()
return comment
return db.run_in_transaction(create_comment_txn)
def post(self):
assert isSet(self.data.proposer)
assert isSet(self.data.proposal)
comment = self.createCommentFromForm()
if comment:
self.redirect.program()
self.redirect.to('gsoc_dashboard')
else:
# This is an insanely and absolutely hacky solution. We definitely
# do not want any one to use this a model for writing code elsewhere
# in Melange.
# TODO (Madhu): Replace this in favor of PJAX for loading comments.
r = self.redirect.review(self.data.proposal.key().id(),
self.data.proposer.link_id)
redirect_url = r.urlOf('review_gsoc_proposal')
proposal_match = resolve(redirect_url)
proposal_view = proposal_match[0]
self.request.method = 'GET'
self.response = proposal_view(self.request, *self.args, **self.kwargs)
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)
class PostScore(RequestHandler):
"""View which handles posting scores.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/score/%s$' % url_patterns.REVIEW,
self, name='score_gsoc_proposal'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
assert isSet(self.data.proposal_org)
org = self.data.proposal_org
if not self.data.orgAdminFor(org) and org.scoring_disabled:
raise BadRequest('Scoring is disabled for this organization')
self.check.isMentorForOrganization(org)
def createOrUpdateScore(self, value):
"""Creates a new score or updates a score if there is already one
posted by the current user.
If the value passed in is 0 then the Score of the user will be removed and
None will be returned.
Args:
value: The value of the score the user gave as an integer.
Returns:
The score entity that was created/updated or None if value is 0.
"""
assert isSet(self.data.proposal)
assert isSet(self.data.proposal_org)
max_score = self.data.proposal_org.max_score
if value < 0 or value > max_score:
raise BadRequest("Score must not be higher than %d" % max_score)
query = db.Query(GSoCScore)
query.filter('author = ', self.data.profile)
query.ancestor(self.data.proposal)
def update_score_trx():
delta = 0
# update score entity
score = query.get()
if not score:
if not value:
return
old_value = 0
score = GSoCScore(
parent=self.data.proposal,
author=self.data.profile,
value=value)
score.put()
delta = 1
else:
old_value = score.value
if not value:
delta = -1
score.delete()
else:
score.value = value
score.put()
# update total score for the proposal
proposal = db.get(self.data.proposal.key())
proposal.score += value - old_value
proposal.nr_scores += delta
proposal.put()
db.run_in_transaction(update_score_trx)
def post(self):
value_str = self.data.POST.get('value', '')
value = int(value_str) if value_str.isdigit() else None
self.createOrUpdateScore(value)
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)
class WishToMentor(RequestHandler):
"""View handling wishing to mentor requests.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/wish_to_mentor/%s$' % url_patterns.REVIEW,
self, name='gsoc_proposal_wish_to_mentor'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
assert isSet(self.data.proposal_org)
self.check.isMentorForOrganization(self.data.proposal_org)
def addToPotentialMentors(self, value):
"""Toggles the user from the potential mentors list.
Args:
value: can be either "checked" or "unchecked".
"""
assert isSet(self.data.profile)
assert isSet(self.data.proposal)
if value != 'checked' and value != 'unchecked':
raise BadRequest("Invalid post data.")
if value == 'checked' and not self.data.isPossibleMentorForProposal():
raise BadRequest("Invalid post data.")
if value == 'unchecked' and self.data.isPossibleMentorForProposal():
raise BadRequest("Invalid post data.")
proposal_key = self.data.proposal.key()
profile_key = self.data.profile.key()
def update_possible_mentors_trx():
# transactionally get latest version of the proposal
proposal = db.get(proposal_key)
if value == 'unchecked':
# we have already been added
if profile_key in proposal.possible_mentors:
return
proposal.possible_mentors.append(profile_key)
else:
# we have already been removed
if profile_key not in proposal.possible_mentors:
return
proposal.possible_mentors.remove(profile_key)
db.put(proposal)
db.run_in_transaction(update_possible_mentors_trx)
def post(self):
value = self.data.POST.get('value')
self.addToPotentialMentors(value)
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)
class AssignMentor(RequestHandler):
"""View which handles assigning mentor to a proposal.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/assign_mentor/%s$' % url_patterns.REVIEW,
self, name='gsoc_proposal_assign_mentor'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
assert isSet(self.data.proposal_org)
self.check.isOrgAdminForOrganization(self.data.proposal_org)
def assignMentor(self, mentor_entity):
"""Assigns the mentor to the proposal.
Args:
mentor_entity: The entity of the mentor profile which needs to assigned
to the proposal.
"""
assert isSet(self.data.proposal)
proposal_key = self.data.proposal.key()
def assign_mentor_txn():
proposal = db.get(proposal_key)
proposal.mentor = mentor_entity
proposal.has_mentor = True
db.put(proposal)
db.run_in_transaction(assign_mentor_txn)
def unassignMentor(self):
"""Removes the mentor assigned to the proposal.
"""
assert isSet(self.data.proposal)
proposal_key = self.data.proposal.key()
def unassign_mentor_txn():
proposal = db.get(proposal_key)
proposal.mentor = None
proposal.has_mentor = False
db.put(proposal)
db.run_in_transaction(unassign_mentor_txn)
def validate(self):
mentor_key = self.data.POST.get('assign_mentor')
if mentor_key:
mentor_entity = db.get(mentor_key)
org = self.data.proposal.org
if mentor_entity and self.data.isPossibleMentorForProposal(
mentor_entity) or (org.list_all_mentors
and db.Key(mentor_key) in profile_logic.queryAllMentorsKeysForOrg(
org)):
return mentor_entity
else:
raise BadRequest("Invalid post data.")
return None
def post(self):
assert isSet(self.data.proposal)
mentor_entity= self.validate()
if mentor_entity:
self.assignMentor(mentor_entity)
else:
self.unassignMentor()
self.data.proposer = self.data.proposal.parent()
self.redirect.review(self.data.proposal.key().id(),
self.data.proposer.link_id)
self.redirect.to('review_gsoc_proposal')
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)
class IgnoreProposal(RequestHandler):
"""View which allows org admins to ignore a proposal.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/ignore/%s$' % url_patterns.REVIEW,
self, name='gsoc_proposal_ignore'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
assert isSet(self.data.proposal_org)
self.check.isOrgAdminForOrganization(self.data.proposal_org)
def toggleIgnoreProposal(self, value):
"""Toggles the ignore status of the proposal.
Args:
value: can be either "checked" or "unchecked".
"""
assert isSet(self.data.proposal)
if value != 'checked' and value != 'unchecked':
raise BadRequest("Invalid post data.")
if value == 'checked' and self.data.proposal.status != 'ignored':
raise BadRequest("Invalid post data.")
if value == 'unchecked' and self.data.proposal.status not in [
'pending', 'withdrawn']:
raise BadRequest("Invalid post data.")
proposal_key = self.data.proposal.key()
def update_status_txn():
# transactionally get latest version of the proposal
proposal = db.get(proposal_key)
if value == 'unchecked':
proposal.status = 'ignored'
elif value == 'checked':
proposal.status = 'pending'
db.put(proposal)
db.run_in_transaction(update_status_txn)
def post(self):
value = self.data.POST.get('value')
self.toggleIgnoreProposal(value)
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)
class ProposalModificationPostDeadline(RequestHandler):
"""View allowing mentors to allow students to modify the proposal.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/modification/%s$' % url_patterns.REVIEW,
self, name='gsoc_proposal_modification'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
assert isSet(self.data.proposal_org)
self.check.isMentorForOrganization(self.data.proposal_org)
def toggleModificationPermission(self, value):
"""Toggles the permission to modify the proposal after proposal deadline.
Args:
value: can be either "checked" or "unchecked".
"""
assert isSet(self.data.proposal)
if value != 'checked' and value != 'unchecked':
raise BadRequest("Invalid post data.")
if value == 'checked' and not self.data.proposal.is_editable_post_deadline:
raise BadRequest("Invalid post data.")
if (value == 'unchecked' and
self.data.proposal.is_editable_post_deadline):
raise BadRequest("Invalid post data.")
proposal_key = self.data.proposal.key()
def update_modification_perm_txn():
# transactionally get latest version of the proposal
proposal = db.get(proposal_key)
if value == 'unchecked':
proposal.is_editable_post_deadline = True
elif value == 'checked':
proposal.is_editable_post_deadline = False
db.put(proposal)
db.run_in_transaction(update_modification_perm_txn)
def post(self):
value = self.data.POST.get('value')
self.toggleModificationPermission(value)
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)
class AcceptProposal(RequestHandler):
"""View allowing org admins to directly accept the proposal.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/accept/%s$' % url_patterns.REVIEW,
self, name='gsoc_proposal_accept'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
assert isSet(self.data.proposal_org)
self.check.isOrgAdminForOrganization(self.data.proposal_org)
def toggleStatus(self, value):
"""Toggles the the application state between accept and pending.
Args:
value: can be either "checked" or "unchecked".
"""
assert isSet(self.data.proposal)
if value != 'checked' and value != 'unchecked':
raise BadRequest("Invalid post data.")
if value == 'checked' and not self.data.proposal.accept_as_project:
raise BadRequest("Invalid post data.")
if value == 'unchecked' and self.data.proposal.accept_as_project:
raise BadRequest("Invalid post data.")
proposal_key = self.data.proposal.key()
def update_status_txn():
# transactionally get latest version of the proposal
proposal = db.get(proposal_key)
if value == 'unchecked':
proposal.accept_as_project = True
elif value == 'checked':
proposal.accept_as_project = False
db.put(proposal)
db.run_in_transaction(update_status_txn)
def post(self):
value = self.data.POST.get('value')
self.toggleStatus(value)
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)
class ProposalPubliclyVisible(RequestHandler):
"""View allowing the proposer to make the proposal publicly visible.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/publicly_visible/%s$' % url_patterns.REVIEW,
self, name='gsoc_proposal_publicly_visible'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
self.check.isProposer()
def togglePublicVisibilty(self, value):
"""Toggles the the public visibility of the application.
Args:
value: can be either "checked" or "unchecked".
"""
assert isSet(self.data.proposal)
if value != 'checked' and value != 'unchecked':
raise BadRequest("Invalid post data.")
if value == 'checked' and not self.data.proposal.is_publicly_visible:
raise BadRequest("Invalid post data.")
if value == 'unchecked' and self.data.proposal.is_publicly_visible:
raise BadRequest("Invalid post data.")
proposal_key = self.data.proposal.key()
def update_publicly_visibility_txn():
# transactionally get latest version of the proposal
proposal = db.get(proposal_key)
if value == 'unchecked':
proposal.is_publicly_visible = True
elif value == 'checked':
proposal.is_publicly_visible = False
db.put(proposal)
db.run_in_transaction(update_publicly_visibility_txn)
def post(self):
value = self.data.POST.get('value')
self.togglePublicVisibilty(value)
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)
class WithdrawProposal(RequestHandler):
"""View allowing the proposer to withdraw the proposal.
"""
def djangoURLPatterns(self):
return [
url(r'proposal/withdraw/%s$' % url_patterns.REVIEW,
self, name='gsoc_proposal_withdraw'),
]
def checkAccess(self):
self.mutator.proposalFromKwargs()
self.check.isProposer()
def toggleWithdrawProposal(self, value):
"""Toggles the the application state between withdraw and pending.
Args:
value: can be either "checked" or "unchecked".
"""
assert isSet(self.data.proposal)
if value != 'checked' and value != 'unchecked':
raise BadRequest("Invalid post data.")
if value == 'checked' and not self.data.proposal.status == 'withdrawn':
raise BadRequest("Invalid post data.")
if value == 'unchecked' and self.data.proposal.status == 'withdrawn':
raise BadRequest("Invalid post data.")
proposal_key = self.data.proposal.key()
def update_withdraw_status_txn():
# transactionally get latest version of the proposal
proposal = db.get(proposal_key)
if value == 'unchecked':
proposal.status = 'withdrawn'
elif value == 'checked':
proposal.status = 'pending'
db.put(proposal)
db.run_in_transaction(update_withdraw_status_txn)
def post(self):
value = self.data.POST.get('value')
self.toggleWithdrawProposal(value)
def get(self):
"""Special Handler for HTTP GET request since this view only handles POST.
"""
self.error(405)