Newer
Older
Fredrik Jonsson
committed
from django.contrib.auth.decorators import login_required
Fredrik Jonsson
committed
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template.loader import get_template
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
Fredrik Jonsson
committed
from django.views.generic import CreateView, ListView, DetailView, DeleteView
from wagtail.core.blocks import RichTextBlock
from opentech.apply.activity.messaging import messenger, MESSAGES
from opentech.apply.funds.models import ApplicationSubmission, AssignedReviewers
from opentech.apply.review.blocks import RecommendationBlock, RecommendationCommentsBlock
from opentech.apply.review.forms import ReviewModelForm, ReviewOpinionForm
from opentech.apply.stream_forms.models import BaseStreamForm
from opentech.apply.users.decorators import staff_required
from opentech.apply.users.groups import REVIEWER_GROUP_NAME
from opentech.apply.utils.views import CreateOrUpdateView
from opentech.apply.utils.image import generate_image_tag
from .models import Review
from .options import DISAGREE
class ReviewContextMixin:
def get_context_data(self, **kwargs):
assigned_reviewers = self.object.assigned.review_order()
if not self.object.stage.has_external_review:
assigned_reviewers = assigned_reviewers.staff()
Erin Mullaney
committed
# Calculate the recommendation based on role and staff reviews
recommendation = self.object.reviews.by_staff().recommendation()
return super().get_context_data(
hidden_types=[REVIEWER_GROUP_NAME],
staff_reviewers_exist=assigned_reviewers.staff().exists(),
assigned_reviewers=assigned_reviewers,
Erin Mullaney
committed
recommendation=recommendation,
def get_fields_for_stage(submission):
forms = submission.get_from_parent('review_forms').all()
index = submission.workflow.stages.index(submission.stage)
return forms[index].form.form_fields
return forms[0].form.form_fields
Fredrik Jonsson
committed
@method_decorator(login_required, name='dispatch')
class ReviewCreateOrUpdateView(BaseStreamForm, CreateOrUpdateView):
submission_form_class = ReviewModelForm
template_name = 'review/review_form.html'
def get_object(self, queryset=None):
return self.model.objects.get(submission=self.submission, author__reviewer=self.request.user)
def dispatch(self, request, *args, **kwargs):
self.submission = get_object_or_404(ApplicationSubmission, id=self.kwargs['submission_pk'])
if not self.submission.phase.permissions.can_review(request.user) or not self.submission.has_permission_to_review(request.user):
raise PermissionDenied()
if self.request.POST and self.submission.reviewed_by(request.user):
return self.get(request, *args, **kwargs)
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
has_submitted_review = self.submission.reviewed_by(self.request.user)
return super().get_context_data(
submission=self.submission,
has_submitted_review=has_submitted_review,
title="Update Review draft" if self.object else 'Create Review',
def get_defined_fields(self):
return get_fields_for_stage(self.submission)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
kwargs['submission'] = self.submission
kwargs['initial'] = self.object.form_data
def form_valid(self, form):
form.instance.form_fields = self.get_defined_fields()
form.instance.author, _ = AssignedReviewers.objects.get_or_create_for_user(
submission=self.submission,
reviewer=self.request.user,
)
response = super().form_valid(form)
if not self.object.is_draft:
messenger(
MESSAGES.NEW_REVIEW,
request=self.request,
submission=self.submission,
related=self.object,
)
return response
def get_success_url(self):
return self.submission.get_absolute_url()
class ReviewDisplay(UserPassesTestMixin, DetailView):
def get_context_data(self, **kwargs):
review = self.get_object()
if review.author.reviewer != self.request.user:
consensus_form = ReviewOpinionForm(
instance=review.opinions.filter(author__reviewer=self.request.user).first(),
)
else:
consensus_form = None
return super().get_context_data(
form=consensus_form,
**kwargs,
)
user = self.request.user
Parbhat Puri
committed
submission = review.submission
partner_has_access = submission.partners.filter(pk=user.pk).exists()
return True
if user == author:
return True
if user.is_reviewer and review.reviewer_visibility:
return True
if user.is_partner and partner_has_access and review.reviewer_visibility and submission.user != user:
return True
if user.is_community_reviewer and submission.community_review and review.reviewer_visibility and submission.user != user:
return True
return False
def dispatch(self, request, *args, **kwargs):
review = self.get_object()
return HttpResponseRedirect(reverse_lazy('apply:submissions:reviews:form', args=(review.submission.id,)))
return super().dispatch(request, *args, **kwargs)
class ReviewOpinionFormView(UserPassesTestMixin, CreateView):
template_name = 'review/review_detail.html'
form_class = ReviewOpinionForm
model = Review
raise_exception = True
def get_form_kwargs(self):
self.object = self.get_object()
kwargs = super().get_form_kwargs()
instance = kwargs['instance']
kwargs['instance'] = instance.opinions.filter(author__reviewer=self.request.user).first()
return kwargs
def test_func(self):
review = self.get_object()
user = self.request.user
author = review.author
submission = review.submission
partner_has_access = submission.partners.filter(pk=user.pk).exists()
if user.is_apply_staff:
return True
if user == author:
return False
if user.is_reviewer and review.reviewer_visibility:
return True
if user.is_partner and partner_has_access and review.reviewer_visibility and submission.user != user:
return True
if user.is_community_reviewer and submission.community_review and review.reviewer_visibility and submission.user != user:
return True
return False
def form_valid(self, form):
self.review = self.get_object()
author, _ = AssignedReviewers.objects.get_or_create_for_user(
submission=self.review.submission,
reviewer=self.request.user,
)
form.instance.author = author
form.instance.review = self.review
response = super().form_valid(form)
opinion = form.instance
messenger(
MESSAGES.REVIEW_OPINION,
request=self.request,
user=self.request.user,
submission=self.review.submission,
return HttpResponseRedirect(reverse_lazy('apply:submissions:reviews:form', args=(self.review.submission.pk,)))
else:
return response
def get_success_url(self):
return self.review.get_absolute_url()
@method_decorator(login_required, name='dispatch')
class ReviewDetailView(DetailView):
def get(self, request, *args, **kwargs):
view = ReviewDisplay.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = ReviewOpinionFormView.as_view()
return view(request, *args, **kwargs)
@method_decorator(staff_required, name='dispatch')
class ReviewListView(ListView):
model = Review
def get_queryset(self):
self.submission = get_object_or_404(ApplicationSubmission, id=self.kwargs['submission_pk'])
self.queryset = self.model.objects.filter(submission=self.submission, is_draft=False)
def should_display(self, field):
return not isinstance(field.block, (RecommendationBlock, RecommendationCommentsBlock, RichTextBlock))
def get_context_data(self, **kwargs):
review_data = {}
# Add the header rows
review_data['title'] = {'question': '', 'answers': list()}
review_data['opinions'] = {'question': 'Opinions', 'answers': list()}
review_data['score'] = {'question': 'Overall Score', 'answers': list()}
review_data['recommendation'] = {'question': 'Recommendation', 'answers': list()}
review_data['revision'] = {'question': 'Revision', 'answers': list()}
review_data['comments'] = {'question': 'Comments', 'answers': list()}
responses = self.object_list.count()
ordered_reviewers = AssignedReviewers.objects.filter(submission=self.submission).reviewed().review_order()
reviews = {review.author: review for review in self.object_list}
for i, reviewer in enumerate(ordered_reviewers):
review = reviews[reviewer]
author = '<a href="{}"><span>{}</span></a>'.format(review.get_absolute_url(), review.author)
if review.author.role:
author += generate_image_tag(review.author.role.icon, '12x12')
author = f'<div>{author}</div>'
review_data['title']['answers'].append(author)
Erin Mullaney
committed
opinions_template = get_template('review/includes/review_opinions_list.html')
opinions_html = opinions_template.render({'opinions': review.opinions.select_related('author').all()})
review_data['opinions']['answers'].append(opinions_html)
review_data['score']['answers'].append(review.get_score_display)
review_data['recommendation']['answers'].append(review.get_recommendation_display())
review_data['comments']['answers'].append(review.get_comments_display(include_question=False))
if review.for_latest:
revision = 'Current'
else:
revision = '<a href="{}">Compare</a>'.format(review.get_compare_url())
review_data['revision']['answers'].append(revision)
for field_id in review.fields:
field = review.field(field_id)
data = review.data(field_id)
question = field.value['field_label']
review_data.setdefault(field.id, {'question': question, 'answers': [''] * responses})
review_data[field.id]['answers'][i] = field.block.render(None, {'data': data})
return super().get_context_data(
submission=self.submission,
review_data=review_data,
**kwargs
)
Fredrik Jonsson
committed
Fredrik Jonsson
committed
class ReviewDeleteView(UserPassesTestMixin, DeleteView):
Fredrik Jonsson
committed
model = Review
Fredrik Jonsson
committed
raise_exception = True
def test_func(self):
review = self.get_object()
return self.request.user.has_perm('review.delete_review') or self.request.user == review.author
Fredrik Jonsson
committed
def delete(self, request, *args, **kwargs):
review = self.get_object()
messenger(
MESSAGES.DELETE_REVIEW,
user=request.user,
request=request,
submission=review.submission,
related=review,
)
response = super().delete(request, *args, **kwargs)
return response
def get_success_url(self):
review = self.get_object()
return reverse_lazy('funds:submissions:detail', args=(review.submission.id,))