symposion_app/vendor/symposion/reviews/views.py

685 lines
25 KiB
Python
Raw Normal View History

import csv
import math
import random
from django.contrib.auth.decorators import login_required
from django.core.mail import send_mass_mail
from django.db import transaction
2012-08-14 07:54:45 +00:00
from django.db.models import Q
from django.http import HttpResponse
from django.http import HttpResponseBadRequest, HttpResponseNotAllowed
2012-08-14 07:54:45 +00:00
from django.shortcuts import render, redirect, get_object_or_404
2012-09-09 01:38:57 +00:00
from django.template import Context, Template
2012-08-14 07:54:45 +00:00
from django.views.decorators.http import require_POST
# @@@ switch to pinax-teams
from symposion.teams.models import Team
2012-08-14 07:54:45 +00:00
2012-08-14 19:52:23 +00:00
from symposion.conf import settings
2012-08-14 07:54:45 +00:00
from symposion.proposals.models import ProposalBase, ProposalSection
2012-08-14 19:52:23 +00:00
from symposion.utils.mail import send_email
2012-08-14 07:54:45 +00:00
from symposion.reviews.forms import ReviewForm, SpeakerCommentForm
from symposion.reviews.forms import BulkPresentationForm
from symposion.reviews.models import (
ReviewAssignment, Review, LatestVote, ProposalResult, NotificationTemplate,
ResultNotification, promote_proposal
)
2012-08-14 07:54:45 +00:00
def access_not_permitted(request):
return render(request, "symposion/reviews/access_not_permitted.html")
2012-08-14 07:54:45 +00:00
def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
for obj in queryset:
# @@@ this sucks; we can do better
if check_speaker:
if request.user in [s.user for s in obj.speakers()]:
continue
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
try:
obj.result
except ProposalResult.DoesNotExist:
ProposalResult.objects.get_or_create(proposal=obj)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
obj.comment_count = obj.result.comment_count
obj.score = obj.result.score
2012-08-14 07:54:45 +00:00
obj.total_votes = obj.result.vote_count
obj.plus_two = obj.result.plus_two
2012-08-14 07:54:45 +00:00
obj.plus_one = obj.result.plus_one
obj.minus_one = obj.result.minus_one
obj.minus_two = obj.result.minus_two
2012-08-14 07:54:45 +00:00
lookup_params = dict(proposal=obj)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if user_pk:
lookup_params["user__pk"] = user_pk
else:
lookup_params["user"] = request.user
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
try:
2012-10-03 22:41:16 +00:00
obj.user_vote = LatestVote.objects.get(**lookup_params).vote
obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class()
2012-08-14 07:54:45 +00:00
except LatestVote.DoesNotExist:
2012-10-03 22:41:16 +00:00
obj.user_vote = None
obj.user_vote_css = "no-vote"
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
yield obj
VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD
POSITIVE = "positive"
NEGATIVE = "negative"
INDIFFERENT = "indifferent"
CONTROVERSIAL = "controversial"
TOO_FEW = "too_few"
REVIEW_STATUS_FILTERS = {
# proposals with at least VOTE_THRESHOLD reviews and at least one +2 and no -2s, sorted by
# the 'score'
POSITIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0,
result__minus_two=0).order_by("-result__score"),
# proposals with at least VOTE_THRESHOLD reviews and at least one -2 and no +2s, reverse
# sorted by the 'score'
NEGATIVE: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two__gt=0,
result__plus_two=0).order_by("result__score"),
# proposals with at least VOTE_THRESHOLD reviews and neither a +2 or a -2, sorted by total
# votes (lowest first)
INDIFFERENT: lambda qs: qs.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_two=0,
result__plus_two=0).order_by("result__vote_count"),
# proposals with at least VOTE_THRESHOLD reviews and both a +2 and -2, sorted by total
# votes (highest first)
CONTROVERSIAL: lambda qs: qs.filter(
result__vote_count__gte=VOTE_THRESHOLD, result__plus_two__gt=0,
result__minus_two__gt=0).order_by("-result__vote_count"),
# proposals with fewer than VOTE_THRESHOLD reviews
TOO_FEW: lambda qs: qs.filter(
result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"),
}
2014-07-30 18:19:26 +00:00
# Returns a list of all proposals, proposals reviewed by the user, or the proposals the user has
# yet to review depending on the link user clicks in dashboard
2012-08-14 07:54:45 +00:00
@login_required
2013-06-11 17:10:29 +00:00
def review_section(request, section_slug, assigned=False, reviewed="all"):
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if not request.user.has_perm("reviews.can_review_%s" % section_slug):
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
section = get_object_or_404(ProposalSection, section__slug=section_slug)
queryset = ProposalBase.objects.filter(kind__section=section.section).order_by('id')
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if assigned:
2014-07-30 18:19:26 +00:00
assignments = ReviewAssignment.objects.filter(user=request.user)\
.values_list("proposal__id")
2012-08-14 07:54:45 +00:00
queryset = queryset.filter(id__in=assignments)
2014-07-30 18:19:26 +00:00
2013-06-11 17:10:29 +00:00
# passing reviewed in from reviews.urls and out to review_list for
# appropriate template header rendering
if reviewed == "all":
queryset = queryset.select_related("result").select_subclasses()
2013-06-11 17:10:29 +00:00
reviewed = "all_reviews"
elif reviewed == "reviewed":
queryset = queryset.filter(reviews__user=request.user)
2013-06-11 17:10:29 +00:00
reviewed = "user_reviewed"
else:
queryset = queryset.exclude(reviews__user=request.user).exclude(
speaker__user=request.user)
2013-06-11 17:10:29 +00:00
reviewed = "user_not_reviewed"
2014-07-30 18:19:26 +00:00
# lca2017 #21 -- chairs want to be able to see their own proposals in the list
check_speaker = not request.user.has_perm("reviews.can_manage_%s" % section_slug)
proposals = proposals_generator(request, queryset, check_speaker=check_speaker)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
ctx = {
"proposals": proposals,
2012-08-14 20:59:50 +00:00
"section": section,
"reviewed": reviewed,
2012-08-14 07:54:45 +00:00
}
2014-07-30 18:19:26 +00:00
return render(request, "symposion/reviews/review_list.html", ctx)
2012-08-14 07:54:45 +00:00
2014-07-30 18:19:26 +00:00
@login_required
def review_proposals_csv(request, section_slug=None):
''' Returns a CSV representation of all of the proposals this user has
permisison to review. '''
filename = "proposals.csv"
if section_slug:
filename = "proposals_{}.csv".format(section_slug)
response = HttpResponse(content_type="text/csv")
response['Content-Disposition'] = 'attachment; filename="{}"'.format(filename)
writer = csv.writer(response, quoting=csv.QUOTE_NONNUMERIC)
queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses()
if section_slug:
queryset = queryset.filter(kind__section__slug=section_slug)
queryset = queryset.order_by("submitted")
proposals = proposals_generator(request, queryset, check_speaker=False)
# The fields from each proposal object to report in the csv
fields = [
"id", "proposal_type", "speaker_name", "speaker_email", "title",
"audience", "submitted", "other_speakers", "speaker_travel",
"speaker_accommodation", "cancelled", "status", "suggested_status",
"score", "total_votes", "minus_two", "minus_one", "plus_one", "plus_two",
]
# Fields are the heading
writer.writerow(fields)
for proposal in proposals:
proposal.speaker_name = proposal.speaker.name
2016-07-04 06:28:31 +00:00
section_slug = proposal.kind.section.slug
kind_slug = proposal.kind.slug
proposal.proposal_type = kind_slug
if hasattr(proposal, "target_audience"):
proposal.audience = proposal.get_target_audience_display()
else:
proposal.audience = "Unknown"
2016-08-25 00:40:21 +00:00
proposal.other_speakers = ", ".join(
speaker.name
for speaker in proposal.additional_speakers.all()
)
proposal.speaker_travel = ", ".join(
str(bool(speaker.travel_assistance))
for speaker in proposal.speakers()
)
proposal.speaker_accommodation = ", ".join(
str(bool(speaker.accommodation_assistance))
for speaker in proposal.speakers()
)
suggested_status = proposal.status
if suggested_status == "undecided":
if proposal.score >= 1.5:
suggested_status = "auto-accept"
elif proposal.score <= -1.5:
suggested_status = "auto-reject"
proposal.suggested_status = suggested_status
2016-07-04 06:28:31 +00:00
if not request.user.has_perm("reviews.can_review_%s" % section_slug):
continue
csv_line = [getattr(proposal, field) for field in fields]
writer.writerow(csv_line)
return response
@login_required
def review_random_proposal(request, section_slug):
# lca2017 #16 view for random proposal
if not request.user.has_perm("reviews.can_review_%s" % section_slug):
return access_not_permitted(request)
section = get_object_or_404(ProposalSection, section__slug=section_slug)
queryset = ProposalBase.objects.filter(kind__section=section.section)
# Remove ones already reviewed
queryset = queryset.exclude(reviews__user=request.user)
# Remove talks the reviewer can't vote on -- their own.
queryset = queryset.exclude(speaker__user=request.user)
queryset = queryset.exclude(additional_speakers__user=request.user)
if len(queryset) == 0:
return redirect("review_section", section_slug=section_slug, reviewed="all")
# Direct reviewers to underreviewed proposals
too_few_set = REVIEW_STATUS_FILTERS[TOO_FEW](queryset)
controversial_set = REVIEW_STATUS_FILTERS[CONTROVERSIAL](queryset)
if len(too_few_set) > 0:
proposals = too_few_set.all()
elif len(controversial_set) > 0 and random.random() < 0.2:
proposals = controversial_set.all()
else:
# Select a proposal with less than the median number of total votes
proposals = proposals_generator(request, queryset, check_speaker=False)
proposals = list(proposals)
proposals.sort(key=lambda proposal: proposal.total_votes)
# The first half is the median or less.
proposals = proposals[:math.ceil(len(proposals) / 2)]
# Realistically, there shouldn't be all that many proposals to choose
# from, so this should be cheap.
chosen = random.choice(proposals)
return redirect("review_detail", pk=chosen.pk)
2012-08-14 07:54:45 +00:00
@login_required
def review_list(request, section_slug, user_pk):
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
# if they're not a reviewer admin and they aren't the person whose
# review list is being asked for, don't let them in
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
if not request.user.pk == user_pk:
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
queryset = ProposalBase.objects.select_related("speaker__user", "result")
reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True)
queryset = queryset.filter(kind__section__slug=section_slug)
2012-08-14 07:54:45 +00:00
queryset = queryset.filter(pk__in=reviewed)
proposals = queryset.order_by("submitted")
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
ctx = {
"proposals": proposals,
}
return render(request, "symposion/reviews/review_list.html", ctx)
2012-08-14 07:54:45 +00:00
@login_required
def review_admin(request, section_slug):
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
def reviewers():
already_seen = set()
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
for team in Team.objects.filter(permissions__codename="can_review_%s" % section_slug):
for membership in team.memberships.filter(Q(state="member") | Q(state="manager")):
user = membership.user
if user.pk in already_seen:
continue
already_seen.add(user.pk)
2014-07-30 18:19:26 +00:00
2016-08-15 23:35:34 +00:00
user.comment_count = Review.objects.filter(
user=user,
proposal__kind__section__slug=section_slug,
).count()
user_votes = LatestVote.objects.filter(
2016-08-15 01:11:25 +00:00
user=user,
proposal__kind__section__slug=section_slug,
)
user.total_votes = user_votes.exclude(
vote=LatestVote.VOTES.ABSTAIN,
2012-08-14 07:54:45 +00:00
).count()
user.plus_two = user_votes.filter(
vote=LatestVote.VOTES.PLUS_TWO,
2012-08-14 07:54:45 +00:00
).count()
user.plus_one = user_votes.filter(
vote=LatestVote.VOTES.PLUS_ONE,
2012-08-14 07:54:45 +00:00
).count()
user.minus_one = user_votes.filter(
vote=LatestVote.VOTES.MINUS_ONE,
).count()
user.minus_two = user_votes.filter(
vote=LatestVote.VOTES.MINUS_TWO,
).count()
user.abstain = user_votes.filter(
vote=LatestVote.VOTES.ABSTAIN,
).count()
2016-08-15 01:11:25 +00:00
if user.total_votes == 0:
user.average = "-"
else:
user.average = (
2016-08-15 23:34:04 +00:00
((user.plus_two * 2) + user.plus_one) -
((user.minus_two * 2) + user.minus_one)
2016-08-15 01:11:25 +00:00
) / (user.total_votes * 1.0)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
yield user
2014-07-30 18:19:26 +00:00
reviewers_sorted = list(reviewers())
reviewers_sorted.sort(key=lambda reviewer: 0 - reviewer.total_votes)
2012-08-14 07:54:45 +00:00
ctx = {
"section_slug": section_slug,
2016-08-15 23:14:50 +00:00
"reviewers": reviewers_sorted,
2012-08-14 07:54:45 +00:00
}
return render(request, "symposion/reviews/review_admin.html", ctx)
2012-08-14 07:54:45 +00:00
# FIXME: This view is too complex according to flake8
2012-08-14 07:54:45 +00:00
@login_required
@transaction.atomic
2012-08-14 07:54:45 +00:00
def review_detail(request, pk):
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
proposals = ProposalBase.objects.select_related("result").select_subclasses()
proposal = get_object_or_404(proposals, pk=pk)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug):
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
speakers = [s.user for s in proposal.speakers()]
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if not request.user.is_superuser and request.user in speakers:
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
admin = request.user.has_perm("reviews.can_manage_%s" % proposal.kind.section.slug)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
try:
latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user)
except LatestVote.DoesNotExist:
latest_vote = None
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if request.method == "POST":
if not admin and request.user in speakers:
2012-08-14 07:54:45 +00:00
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
if "vote_submit" in request.POST or "vote_submit_and_random" in request.POST:
2012-08-14 07:54:45 +00:00
review_form = ReviewForm(request.POST)
if review_form.is_valid():
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
review = review_form.save(commit=False)
review.user = request.user
review.proposal = proposal
review.save()
2014-07-30 18:19:26 +00:00
if "vote_submit_and_random" in request.POST:
next_page = redirect("user_random", proposal.kind.section.slug)
next_page["Location"] += "#invalid_fragment" # Hack.
else:
next_page = redirect(request.path)
return next_page
2012-08-14 07:54:45 +00:00
else:
message_form = SpeakerCommentForm()
elif "message_submit" in request.POST:
2012-08-14 07:54:45 +00:00
message_form = SpeakerCommentForm(request.POST)
if message_form.is_valid():
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
message = message_form.save(commit=False)
message.user = request.user
message.proposal = proposal
message.save()
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
for speaker in speakers:
if speaker and speaker.email:
ctx = {
"proposal": proposal,
"message": message,
"reviewer": False,
}
send_email(
[speaker.email], "proposal_new_message",
2014-07-30 18:19:26 +00:00
context=ctx
2012-08-14 07:54:45 +00:00
)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
return redirect(request.path)
else:
initial = {}
if latest_vote:
initial["vote"] = latest_vote.vote
if request.user in speakers:
review_form = None
else:
review_form = ReviewForm(initial=initial)
elif "result_submit" in request.POST:
if admin:
result = request.POST["result_submit"]
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if result == "accept":
proposal.result.status = "accepted"
2012-08-14 07:54:45 +00:00
proposal.result.save()
elif result == "reject":
proposal.result.status = "rejected"
2012-08-14 07:54:45 +00:00
proposal.result.save()
elif result == "undecide":
proposal.result.status = "undecided"
proposal.result.save()
elif result == "standby":
proposal.result.status = "standby"
2012-08-14 07:54:45 +00:00
proposal.result.save()
return redirect(request.path)
elif "publish_changes" in request.POST:
if admin and proposal.result.status == "accepted":
promote_proposal(proposal)
return redirect(request.path)
2012-08-14 07:54:45 +00:00
else:
initial = {}
if latest_vote:
initial["vote"] = latest_vote.vote
if request.user in speakers:
review_form = None
else:
review_form = ReviewForm(initial=initial)
message_form = SpeakerCommentForm()
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
proposal.comment_count = proposal.result.comment_count
proposal.total_votes = proposal.result.vote_count
proposal.plus_two = proposal.result.plus_two
2012-08-14 07:54:45 +00:00
proposal.plus_one = proposal.result.plus_one
proposal.minus_one = proposal.result.minus_one
proposal.minus_two = proposal.result.minus_two
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at")
messages = proposal.messages.order_by("submitted_at")
2014-07-30 18:19:26 +00:00
return render(request, "symposion/reviews/review_detail.html", {
2012-08-14 07:54:45 +00:00
"proposal": proposal,
"latest_vote": latest_vote,
"reviews": reviews,
"review_messages": messages,
"review_form": review_form,
"message_form": message_form,
"is_manager": admin
2012-08-14 07:54:45 +00:00
})
@login_required
@require_POST
@transaction.atomic
2012-08-14 07:54:45 +00:00
def review_delete(request, pk):
review = get_object_or_404(Review, pk=pk)
2016-06-19 08:38:14 +00:00
section_slug = review.section
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
review = get_object_or_404(Review, pk=pk)
review.delete()
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
return redirect("review_detail", pk=review.proposal.pk)
@login_required
def review_status(request, section_slug=None, key=None):
2014-07-30 18:19:26 +00:00
2012-08-25 23:52:07 +00:00
if not request.user.has_perm("reviews.can_review_%s" % section_slug):
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
ctx = {
2012-08-14 19:52:23 +00:00
"section_slug": section_slug,
"vote_threshold": VOTE_THRESHOLD,
2012-08-14 07:54:45 +00:00
}
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses()
if section_slug:
queryset = queryset.filter(kind__section__slug=section_slug)
2014-07-30 18:19:26 +00:00
proposals = dict((key, filt(queryset)) for key, filt in REVIEW_STATUS_FILTERS.items())
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
2014-07-30 18:19:26 +00:00
for status in proposals:
if key and key != status:
continue
proposals[status] = list(proposals_generator(request, proposals[status], check_speaker=not admin))
2014-07-30 18:19:26 +00:00
2012-08-14 07:54:45 +00:00
if key:
ctx.update({
"key": key,
"proposals": proposals[key],
2012-08-14 07:54:45 +00:00
})
else:
ctx["proposals"] = proposals
2014-07-30 18:19:26 +00:00
return render(request, "symposion/reviews/review_stats.html", ctx)
2012-08-14 07:54:45 +00:00
@login_required
def review_assignments(request):
if not request.user.groups.filter(name="reviewers").exists():
return access_not_permitted(request)
assignments = ReviewAssignment.objects.filter(
user=request.user,
opted_out=False
)
return render(request, "symposion/reviews/review_assignment.html", {
2012-08-14 07:54:45 +00:00
"assignments": assignments,
})
@login_required
@require_POST
def review_assignment_opt_out(request, pk):
2014-07-30 18:19:26 +00:00
review_assignment = get_object_or_404(
ReviewAssignment, pk=pk, user=request.user)
2012-08-14 07:54:45 +00:00
if not review_assignment.opted_out:
review_assignment.opted_out = True
review_assignment.save()
2014-07-30 18:19:26 +00:00
ReviewAssignment.create_assignments(
review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER)
2012-08-14 07:54:45 +00:00
return redirect("review_assignments")
@login_required
@transaction.atomic
def review_bulk_update(request, section_slug):
2012-08-14 07:54:45 +00:00
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
if request.method == "POST":
form = BulkPresentationForm(request.POST)
if form.is_valid():
talk_ids = form.cleaned_data["talk_ids"].split(",")
status = form.cleaned_data["status"] or "accepted"
2012-08-14 07:54:45 +00:00
talks = ProposalBase.objects.filter(id__in=talk_ids).select_related("result")
for talk in talks:
talk.result.status = status
2012-08-14 07:54:45 +00:00
talk.result.save()
2012-12-20 06:50:07 +00:00
return redirect("review_section", section_slug=section_slug)
2012-08-14 07:54:45 +00:00
else:
form = BulkPresentationForm()
2014-07-30 18:19:26 +00:00
return render(request, "symposion/reviews/review_bulk_update.html", {
2012-08-14 07:54:45 +00:00
"form": form,
})
@login_required
def result_notification(request, section_slug, status):
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, result__status=status).select_related("speaker__user", "result").select_subclasses()
notification_templates = NotificationTemplate.objects.all()
2014-07-30 18:19:26 +00:00
ctx = {
"section_slug": section_slug,
"status": status,
"proposals": proposals,
"notification_templates": notification_templates,
}
return render(request, "symposion/reviews/result_notification.html", ctx)
@login_required
def result_notification_prepare(request, section_slug, status):
if request.method != "POST":
return HttpResponseNotAllowed(["POST"])
2014-07-30 18:19:26 +00:00
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
proposal_pks = []
try:
for pk in request.POST.getlist("_selected_action"):
proposal_pks.append(int(pk))
except ValueError:
return HttpResponseBadRequest()
proposals = ProposalBase.objects.filter(
kind__section__slug=section_slug,
result__status=status,
)
proposals = proposals.filter(pk__in=proposal_pks)
proposals = proposals.select_related("speaker__user", "result")
proposals = proposals.select_subclasses()
2014-07-30 18:19:26 +00:00
notification_template_pk = request.POST.get("notification_template", "")
if notification_template_pk:
notification_template = NotificationTemplate.objects.get(pk=notification_template_pk)
else:
notification_template = None
2014-07-30 18:19:26 +00:00
ctx = {
"section_slug": section_slug,
"status": status,
"notification_template": notification_template,
"proposals": proposals,
"proposal_pks": ",".join([str(pk) for pk in proposal_pks]),
}
return render(request, "symposion/reviews/result_notification_prepare.html", ctx)
@login_required
def result_notification_send(request, section_slug, status):
if request.method != "POST":
return HttpResponseNotAllowed(["POST"])
2014-07-30 18:19:26 +00:00
if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
return access_not_permitted(request)
2014-07-30 18:19:26 +00:00
if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]):
return HttpResponseBadRequest()
2014-07-30 18:19:26 +00:00
try:
proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")]
except ValueError:
return HttpResponseBadRequest()
2014-07-30 18:19:26 +00:00
proposals = ProposalBase.objects.filter(
kind__section__slug=section_slug,
result__status=status,
)
proposals = proposals.filter(pk__in=proposal_pks)
proposals = proposals.select_related("speaker__user", "result")
proposals = proposals.select_subclasses()
2014-07-30 18:19:26 +00:00
notification_template_pk = request.POST.get("notification_template", "")
if notification_template_pk:
notification_template = NotificationTemplate.objects.get(pk=notification_template_pk)
else:
notification_template = None
2014-07-30 18:19:26 +00:00
emails = []
2014-07-30 18:19:26 +00:00
for proposal in proposals:
rn = ResultNotification()
rn.proposal = proposal
rn.template = notification_template
rn.to_address = proposal.speaker_email
rn.from_address = request.POST["from_address"]
proposal_context = proposal.notification_email_context()
rn.subject = Template(request.POST["subject"]).render(
Context({
"proposal": proposal_context
})
)
2012-09-09 01:38:57 +00:00
rn.body = Template(request.POST["body"]).render(
Context({
"proposal": proposal_context
2012-09-09 01:38:57 +00:00
})
)
rn.save()
emails.append(rn.email_args)
2014-07-30 18:19:26 +00:00
send_mass_mail(emails)
2014-07-30 18:19:26 +00:00
return redirect("result_notification", section_slug=section_slug, status=status)