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
from django.views.generic import CreateView, ListView, DetailView, DeleteView, UpdateView
from wagtail.core.blocks import RichTextBlock
from opentech.apply.activity.messaging import messenger, MESSAGES
from opentech.apply.funds.models import ApplicationSubmission, AssignedReviewers
Fredrik Jonsson
committed
from opentech.apply.funds.workflow import INITIAL_STATE
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
class ReviewEditView(UserPassesTestMixin, BaseStreamForm, UpdateView):
submission_form_class = ReviewModelForm
model = Review
template_name = 'review/review_edit_form.html'
raise_exception = True
def test_func(self):
review = self.get_object()
return self.request.user.has_perm('review.change_review') or self.request.user == review.author.reviewer
def get_context_data(self, **kwargs):
review = self.get_object()
return super().get_context_data(
submission=review.submission,
title="Edit Review",
**kwargs
)
def get_defined_fields(self):
review = self.get_object()
return get_fields_for_stage(review.submission)
def get_form_kwargs(self):
review = self.get_object()
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
kwargs['submission'] = review.submission
if self.object:
kwargs['initial'] = self.object.form_data
return kwargs
def form_valid(self, form):
review = self.get_object()
messenger(
MESSAGES.EDIT_REVIEW,
user=self.request.user,
request=self.request,
related=review,
)
response = super().form_valid(form)
Fredrik Jonsson
committed
# Automatic workflow actions.
review_workflow_actions(self.request, review.submission)
return response
def get_success_url(self):
review = self.get_object()
return reverse_lazy('funds:submissions:detail', args=(review.submission.id,))
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,
related=self.object,
Fredrik Jonsson
committed
Fredrik Jonsson
committed
# Automatic workflow actions.
Fredrik Jonsson
committed
review_workflow_actions(self.request, self.submission)
Fredrik Jonsson
committed
return response
def get_success_url(self):
return self.submission.get_absolute_url()
Fredrik Jonsson
committed
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
def review_workflow_actions(request, submission):
submission_stepped_phases = submission.workflow.stepped_phases
action = None
if submission.status == INITIAL_STATE:
# Automatically transition the application to "Internal review".
action = submission_stepped_phases[1][0].name
elif submission.status == 'proposal_discussion':
# Automatically transition the proposal to "Internal review".
action = 'proposal_internal_review'
elif submission.status == submission_stepped_phases[1][0].name and submission.reviews.count() > 1:
# Automatically transition the application to "Ready for discussion".
action = submission_stepped_phases[2][0].name
elif submission.status == 'ext_external_review' and submission.reviews.by_reviewers().count() > 1:
# Automatically transition the application to "Ready for discussion".
action = 'ext_post_external_review_discussion'
elif submission.status == 'com_external_review' and submission.reviews.by_reviewers().count() > 1:
# Automatically transition the application to "Ready for discussion".
action = 'com_post_external_review_discussion'
elif submission.status == 'external_review' and submission.reviews.by_reviewers().count() > 1:
# Automatically transition the proposal to "Ready for discussion".
action = 'post_external_review_discussion'
# If action is set run perform_transition().
if action:
try:
submission.perform_transition(
action,
request.user,
request=request,
notify=False,
)
except (PermissionDenied, KeyError):
pass
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
author = review.author.reviewer
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.reviewer
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,
source=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,
source=review.submission,
Fredrik Jonsson
committed
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,))