diff --git a/symposion/reviews/views.py b/symposion/reviews/views.py index 880e9e8b4786f8285255c5b4ff06cfbc66121a16..2be745e5234fbfbce58574cf6f3317bae4f9ae72 100644 --- a/symposion/reviews/views.py +++ b/symposion/reviews/views.py @@ -1,5 +1,3 @@ -import re - from django.core.mail import send_mass_mail from django.db.models import Q from django.http import HttpResponseBadRequest, HttpResponseNotAllowed @@ -27,18 +25,18 @@ def access_not_permitted(request): 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 @@ -46,37 +44,38 @@ def proposals_generator(request, queryset, user_pk=None, check_speaker=True): 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.user_vote = LatestVote.objects.get(**lookup_params).vote obj.user_vote_css = LatestVote.objects.get(**lookup_params).css_class() except LatestVote.DoesNotExist: obj.user_vote = None obj.user_vote_css = "no-vote" - + yield obj -# 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 +# 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 @login_required def review_section(request, section_slug, assigned=False, reviewed="all"): - + 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") + assignments = ReviewAssignment.objects.filter(user=request.user)\ + .values_list("proposal__id") queryset = queryset.filter(id__in=assignments) - + # passing reviewed in from reviews.urls and out to review_list for # appropriate template header rendering if reviewed == "all": @@ -88,35 +87,36 @@ def review_section(request, section_slug, assigned=False, reviewed="all"): else: queryset = queryset.exclude(reviews__user=request.user).exclude(speaker=request.user) reviewed = "user_not_reviewed" - + proposals = proposals_generator(request, queryset) - + ctx = { "proposals": proposals, "section": section, "reviewed": reviewed, } - + 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, } @@ -125,41 +125,41 @@ def review_list(request, section_slug, user_pk): @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 + user=user, + vote=LatestVote.VOTES.PLUS_ONE ).count() user.plus_zero = LatestVote.objects.filter( - user = user, - vote = LatestVote.VOTES.PLUS_ZERO + user=user, + vote=LatestVote.VOTES.PLUS_ZERO ).count() user.minus_zero = LatestVote.objects.filter( - user = user, - vote = LatestVote.VOTES.MINUS_ZERO + user=user, + vote=LatestVote.VOTES.MINUS_ZERO ).count() user.minus_one = LatestVote.objects.filter( - user = user, - vote = LatestVote.VOTES.MINUS_ONE + user=user, + vote=LatestVote.VOTES.MINUS_ONE ).count() - + yield user - + ctx = { "section_slug": section_slug, "reviewers": reviewers(), @@ -169,50 +169,50 @@ def review_admin(request, section_slug): @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 = { @@ -222,9 +222,9 @@ def review_detail(request, pk): } send_email( [speaker.email], "proposal_new_message", - context = ctx + context=ctx ) - + return redirect(request.path) else: initial = {} @@ -237,7 +237,7 @@ def review_detail(request, pk): elif "result_submit" in request.POST: if admin: result = request.POST["result_submit"] - + if result == "accept": proposal.result.status = "accepted" proposal.result.save() @@ -250,7 +250,7 @@ def review_detail(request, pk): elif result == "standby": proposal.result.status = "standby" proposal.result.save() - + return redirect(request.path) else: initial = {} @@ -261,17 +261,17 @@ def review_detail(request, pk): 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, @@ -287,53 +287,64 @@ def review_detail(request, pk): 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 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"), + "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)) - + proposals[status] = list(proposals_generator(request, proposals[status], + check_speaker=not admin)) + if key: ctx.update({ "key": key, @@ -341,7 +352,7 @@ def review_status(request, section_slug=None, key=None): }) else: ctx["proposals"] = proposals - + return render(request, "reviews/review_stats.html", ctx) @@ -361,14 +372,13 @@ def review_assignments(request): @login_required @require_POST def review_assignment_opt_out(request, pk): - review_assignment = get_object_or_404(ReviewAssignment, - pk=pk, - user=request.user - ) + 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) + ReviewAssignment.create_assignments( + review_assignment.proposal, origin=ReviewAssignment.AUTO_ASSIGNED_LATER) return redirect("review_assignments") @@ -387,7 +397,7 @@ def review_bulk_accept(request, section_slug): return redirect("review_section", section_slug=section_slug) else: form = BulkPresentationForm() - + return render(request, "reviews/review_bulk_accept.html", { "form": form, }) @@ -397,10 +407,12 @@ def review_bulk_accept(request, section_slug): def result_notification(request, section_slug, status): if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) - - proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, result__status=status).select_related("speaker__user", "result").select_subclasses() + + proposals = ProposalBase.objects.filter(kind__section__slug=section_slug, + result__status=status)\ + .select_related("speaker__user", "result").select_subclasses() notification_templates = NotificationTemplate.objects.all() - + ctx = { "section_slug": section_slug, "status": status, @@ -414,10 +426,10 @@ def result_notification(request, section_slug, status): def result_notification_prepare(request, section_slug, status): if request.method != "POST": return HttpResponseNotAllowed(["POST"]) - + if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) - + proposal_pks = [] try: for pk in request.POST.getlist("_selected_action"): @@ -431,13 +443,13 @@ def result_notification_prepare(request, section_slug, status): proposals = proposals.filter(pk__in=proposal_pks) proposals = proposals.select_related("speaker__user", "result") proposals = proposals.select_subclasses() - + 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 - + ctx = { "section_slug": section_slug, "status": status, @@ -452,18 +464,18 @@ def result_notification_prepare(request, section_slug, status): def result_notification_send(request, section_slug, status): if request.method != "POST": return HttpResponseNotAllowed(["POST"]) - + if not request.user.has_perm("reviews.can_manage_%s" % section_slug): return access_not_permitted(request) - + if not all([k in request.POST for k in ["proposal_pks", "from_address", "subject", "body"]]): return HttpResponseBadRequest() - + try: proposal_pks = [int(pk) for pk in request.POST["proposal_pks"].split(",")] except ValueError: return HttpResponseBadRequest() - + proposals = ProposalBase.objects.filter( kind__section__slug=section_slug, result__status=status, @@ -471,15 +483,15 @@ def result_notification_send(request, section_slug, status): proposals = proposals.filter(pk__in=proposal_pks) proposals = proposals.select_related("speaker__user", "result") proposals = proposals.select_subclasses() - + 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 - + emails = [] - + for proposal in proposals: rn = ResultNotification() rn.proposal = proposal @@ -494,7 +506,7 @@ def result_notification_send(request, section_slug, status): ) rn.save() emails.append(rn.email_args) - + send_mass_mail(emails) - + return redirect("result_notification", section_slug=section_slug, status=status)