Files @ 14870dd5b0ca
Branch filter:

Location: symposion_app/symposion/reviews/views.py - annotation

James Tauber
removed old facebox reference
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
0aca96152f65
3d68af979659
0aca96152f65
0aca96152f65
0aca96152f65
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
c7dd6e6f8d2e
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
1cb22cea953d
1cb22cea953d
1cb22cea953d
0aca96152f65
0aca96152f65
3d68af979659
0aca96152f65
0aca96152f65
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
0aca96152f65
0aca96152f65
0aca96152f65
0aca96152f65
0aca96152f65
0aca96152f65
0aca96152f65
0aca96152f65
0aca96152f65
0aca96152f65
3d68af979659
6ee3ff5d4599
3d68af979659
3d68af979659
6ee3ff5d4599
54991cd4d187
54991cd4d187
6ee3ff5d4599
6ee3ff5d4599
3d68af979659
3d68af979659
3d68af979659
6ee3ff5d4599
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
3d68af979659
from django.db.models import Q
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST

from django.contrib.auth.decorators import login_required

from symposion.conf import settings
from symposion.proposals.models import ProposalBase, ProposalSection
from symposion.teams.models import Team
from symposion.utils.mail import send_email

from symposion.reviews.forms import ReviewForm, SpeakerCommentForm
from symposion.reviews.forms import BulkPresentationForm
from symposion.reviews.models import ReviewAssignment, Review, LatestVote, ProposalResult


def access_not_permitted(request):
    return render(request, "reviews/access_not_permitted.html")


def proposals_generator(request, queryset, user_pk=None, check_speaker=True):
    
    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
        
        try:
            obj.result
        except ProposalResult.DoesNotExist:
            ProposalResult.objects.get_or_create(proposal=obj)
        
        obj.comment_count = obj.result.comment_count
        obj.total_votes = obj.result.vote_count
        obj.plus_one = obj.result.plus_one
        obj.plus_zero = obj.result.plus_zero
        obj.minus_zero = obj.result.minus_zero
        obj.minus_one = obj.result.minus_one
        lookup_params = dict(proposal=obj)
        
        if user_pk:
            lookup_params["user__pk"] = user_pk
        else:
            lookup_params["user"] = request.user
        
        try:
            obj.latest_vote = LatestVote.objects.get(**lookup_params).css_class()
        except LatestVote.DoesNotExist:
            obj.latest_vote = "no-vote"
        
        yield obj


@login_required
def review_section(request, section_slug, assigned=False):
    
    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)
    
    if assigned:
        assignments = ReviewAssignment.objects.filter(user=request.user).values_list("proposal__id")
        queryset = queryset.filter(id__in=assignments)
    
    queryset = queryset.select_related("result").select_subclasses()
    
    proposals = proposals_generator(request, queryset)
    
    ctx = {
        "proposals": proposals,
        "section": section,
    }
    
    return render(request, "reviews/review_list.html", ctx)


@login_required
def review_list(request, section_slug, user_pk):
    
    # 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)
    
    queryset = ProposalBase.objects.select_related("speaker__user", "result")
    reviewed = LatestVote.objects.filter(user__pk=user_pk).values_list("proposal", flat=True)
    queryset = queryset.filter(pk__in=reviewed)
    proposals = queryset.order_by("submitted")
    
    admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
    
    proposals = proposals_generator(request, proposals, user_pk=user_pk, check_speaker=not admin)
    
    ctx = {
        "proposals": proposals,
    }
    return render(request, "reviews/review_list.html", ctx)


@login_required
def review_admin(request, section_slug):
    
    if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
        return access_not_permitted(request)
    
    def reviewers():
        already_seen = set()
        
        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)
                
                user.comment_count = Review.objects.filter(user=user).count()
                user.total_votes = LatestVote.objects.filter(user=user).count()
                user.plus_one = LatestVote.objects.filter(
                    user = user,
                    vote = LatestVote.VOTES.PLUS_ONE
                ).count()
                user.plus_zero = LatestVote.objects.filter(
                    user = user,
                    vote = LatestVote.VOTES.PLUS_ZERO
                ).count()
                user.minus_zero = LatestVote.objects.filter(
                    user = user,
                    vote = LatestVote.VOTES.MINUS_ZERO
                ).count()
                user.minus_one = LatestVote.objects.filter(
                    user = user,
                    vote = LatestVote.VOTES.MINUS_ONE
                ).count()
                
                yield user
    
    ctx = {
        "section_slug": section_slug,
        "reviewers": reviewers(),
    }
    return render(request, "reviews/review_admin.html", ctx)


@login_required
def review_detail(request, pk):
    
    proposals = ProposalBase.objects.select_related("result").select_subclasses()
    proposal = get_object_or_404(proposals, pk=pk)
    
    if not request.user.has_perm("reviews.can_review_%s" % proposal.kind.section.slug):
        return access_not_permitted(request)
    
    speakers = [s.user for s in proposal.speakers()]
    
    if not request.user.is_superuser and request.user in speakers:
        return access_not_permitted(request)
    
    admin = request.user.is_staff
    
    try:
        latest_vote = LatestVote.objects.get(proposal=proposal, user=request.user)
    except LatestVote.DoesNotExist:
        latest_vote = None
    
    if request.method == "POST":
        if request.user in speakers:
            return access_not_permitted(request)
        
        if "vote_submit" in request.POST:
            review_form = ReviewForm(request.POST)
            if review_form.is_valid():
                
                review = review_form.save(commit=False)
                review.user = request.user
                review.proposal = proposal
                review.save()
                
                return redirect(request.path)
            else:
                message_form = SpeakerCommentForm()
        elif "message_submit" in request.POST:
            message_form = SpeakerCommentForm(request.POST)
            if message_form.is_valid():
                
                message = message_form.save(commit=False)
                message.user = request.user
                message.proposal = proposal
                message.save()
                
                for speaker in speakers:
                    if speaker and speaker.email:
                        ctx = {
                            "proposal": proposal,
                            "message": message,
                            "reviewer": False,
                        }
                        send_email(
                            [speaker.email], "proposal_new_message",
                            context = ctx
                        )
                
                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"]
                
                if result == "accept":
                    proposal.result.accepted = True
                    proposal.result.save()
                elif result == "reject":
                    proposal.result.accepted = False
                    proposal.result.save()
                elif result == "undecide":
                    proposal.result.accepted = None
                    proposal.result.save()
            
            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)
        message_form = SpeakerCommentForm()
    
    proposal.comment_count = proposal.result.comment_count
    proposal.total_votes = proposal.result.vote_count
    proposal.plus_one = proposal.result.plus_one
    proposal.plus_zero = proposal.result.plus_zero
    proposal.minus_zero = proposal.result.minus_zero
    proposal.minus_one = proposal.result.minus_one
    
    reviews = Review.objects.filter(proposal=proposal).order_by("-submitted_at")
    messages = proposal.messages.order_by("submitted_at")
    
    return render(request, "reviews/review_detail.html", {
        "proposal": proposal,
        "latest_vote": latest_vote,
        "reviews": reviews,
        "review_messages": messages,
        "review_form": review_form,
        "message_form": message_form
    })


@login_required
@require_POST
def review_delete(request, pk):
    review = get_object_or_404(Review, pk=pk)
    section_slug = review.section.slug
    
    if not request.user.has_perm("reviews.can_manage_%s" % section_slug):
        return access_not_permitted(request)
    
    review = get_object_or_404(Review, pk=pk)
    review.delete()
    
    return redirect("review_detail", pk=review.proposal.pk)


@login_required
def review_status(request, section_slug=None, key=None):
    
    if not request.user.has_perm("reviews.can_review_%s" % section_slug):
        return access_not_permitted(request)
    
    VOTE_THRESHOLD = settings.SYMPOSION_VOTE_THRESHOLD
    
    ctx = {
        "section_slug": section_slug,
        "vote_threshold": VOTE_THRESHOLD,
    }
    
    queryset = ProposalBase.objects.select_related("speaker__user", "result").select_subclasses()
    if section_slug:
        queryset = queryset.filter(kind__section__slug=section_slug)
    
    proposals = {
        # proposals with at least VOTE_THRESHOLD reviews and at least one +1 and no -1s, sorted by the 'score'
        "positive": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one=0).order_by("-result__score"),
        # proposals with at least VOTE_THRESHOLD reviews and at least one -1 and no +1s, reverse sorted by the 'score'
        "negative": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one__gt=0, result__plus_one=0).order_by("result__score"),
        # proposals with at least VOTE_THRESHOLD reviews and neither a +1 or a -1, sorted by total votes (lowest first)
        "indifferent": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__minus_one=0, result__plus_one=0).order_by("result__vote_count"),
        # proposals with at least VOTE_THRESHOLD reviews and both a +1 and -1, sorted by total votes (highest first)
        "controversial": queryset.filter(result__vote_count__gte=VOTE_THRESHOLD, result__plus_one__gt=0, result__minus_one__gt=0).order_by("-result__vote_count"),
        # proposals with fewer than VOTE_THRESHOLD reviews
        "too_few": queryset.filter(result__vote_count__lt=VOTE_THRESHOLD).order_by("result__vote_count"),
    }
    
    admin = request.user.has_perm("reviews.can_manage_%s" % section_slug)
    
    for status in proposals:
        if key and key != status:
            continue
        proposals[status] = list(proposals_generator(request, proposals[status], check_speaker=not admin))
    
    if key:
        ctx.update({
            "key": key,
            "proposals": proposals[key],
        })
    else:
        ctx["proposals"] = proposals
    
    return render(request, "reviews/review_stats.html", ctx)


@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, "reviews/review_assignment.html", {
        "assignments": assignments,
    })


@login_required
@require_POST
def review_assignment_opt_out(request, pk):
    review_assignment = get_object_or_404(ReviewAssignment,
        pk=pk,
        user=request.user
    )
    if not review_assignment.opted_out:
        review_assignment.opted_out = True
        review_assignment.save()
        ReviewAssignment.create_assignments(review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER)
    return redirect("review_assignments")


@login_required
def review_bulk_accept(request, section_slug):
    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(",")
            talks = ProposalBase.objects.filter(id__in=talk_ids).select_related("result")
            for talk in talks:
                talk.result.accepted = True
                talk.result.save()
            return redirect("review_list")
    else:
        form = BulkPresentationForm()
    
    return render(request, "reviews/review_bulk_accept.html", {
        "form": form,
    })