diff --git a/opentech/apply/activity/messaging.py b/opentech/apply/activity/messaging.py index 084dc735497bf38978988518acc5b736eb2bda13..eb6405e039f5642b97f60e257659e893ce728d10 100644 --- a/opentech/apply/activity/messaging.py +++ b/opentech/apply/activity/messaging.py @@ -55,6 +55,7 @@ neat_related = { MESSAGES.SCREENING: 'old_status', MESSAGES.REVIEW_OPINION: 'opinion', MESSAGES.DELETE_REVIEW: 'review', + MESSAGES.EDIT_REVIEW: 'review', } @@ -369,6 +370,7 @@ class SlackAdapter(AdapterBase): MESSAGES.BATCH_READY_FOR_REVIEW: 'batch_notify_reviewers', MESSAGES.DELETE_SUBMISSION: '{user} has deleted {submission.title}', MESSAGES.DELETE_REVIEW: '{user} has deleted {review.author} review for <{link}|{submission.title}>.', + MESSAGES.EDIT_REVIEW: '{user} has edited {review.author} review for <{link}|{submission.title}>.', } def __init__(self): diff --git a/opentech/apply/activity/migrations/0024_add_review_edit_event.py b/opentech/apply/activity/migrations/0024_add_review_edit_event.py new file mode 100644 index 0000000000000000000000000000000000000000..e1387495ab149a6d52d161c1b880e3b4bda4dadf --- /dev/null +++ b/opentech/apply/activity/migrations/0024_add_review_edit_event.py @@ -0,0 +1,18 @@ +# Generated by Django 2.0.13 on 2019-07-14 09:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('activity', '0023_notify_partners'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='type', + field=models.CharField(choices=[('UPDATE_LEAD', 'Update Lead'), ('EDIT', 'Edit'), ('APPLICANT_EDIT', 'Applicant Edit'), ('NEW_SUBMISSION', 'New Submission'), ('SCREENING', 'Screening'), ('TRANSITION', 'Transition'), ('BATCH_TRANSITION', 'Batch Transition'), ('DETERMINATION_OUTCOME', 'Determination Outcome'), ('BATCH_DETERMINATION_OUTCOME', 'Batch Determination Outcome'), ('INVITED_TO_PROPOSAL', 'Invited To Proposal'), ('REVIEWERS_UPDATED', 'Reviewers Updated'), ('BATCH_REVIEWERS_UPDATED', 'Batch Reviewers Updated'), ('PARTNERS_UPDATED', 'Partners Updated'), ('PARTNERS_UPDATED_PARTNER', 'Partners Updated Partner'), ('READY_FOR_REVIEW', 'Ready For Review'), ('BATCH_READY_FOR_REVIEW', 'Batch Ready For Review'), ('NEW_REVIEW', 'New Review'), ('COMMENT', 'Comment'), ('PROPOSAL_SUBMITTED', 'Proposal Submitted'), ('OPENED_SEALED', 'Opened Sealed Submission'), ('REVIEW_OPINION', 'Review Opinion'), ('DELETE_SUBMISSION', 'Delete Submission'), ('DELETE_REVIEW', 'Delete Review'), ('EDIT_REVIEW', 'Edit Review')], max_length=50), + ), + ] diff --git a/opentech/apply/activity/options.py b/opentech/apply/activity/options.py index 6e9a914cf5025a84da0b72d3e2cb54993b57f3f5..1fd7988a748fff6ca610a28d3f8c6210913204f5 100644 --- a/opentech/apply/activity/options.py +++ b/opentech/apply/activity/options.py @@ -26,6 +26,7 @@ class MESSAGES(Enum): REVIEW_OPINION = 'Review Opinion' DELETE_SUBMISSION = 'Delete Submission' DELETE_REVIEW = 'Delete Review' + EDIT_REVIEW = 'Edit Review' @classmethod def choices(cls): diff --git a/opentech/apply/review/templates/review/review_detail.html b/opentech/apply/review/templates/review/review_detail.html index 33d224fc9ef888f3cca1eb9ce70e44037f934db0..20264332d8a1482f0609d56d7be9784e5d3af604 100644 --- a/opentech/apply/review/templates/review/review_detail.html +++ b/opentech/apply/review/templates/review/review_detail.html @@ -24,7 +24,7 @@ <svg class="icon icon--eye"><use xlink:href="#eye"></use></svg> {{ review.get_visibility_display }} </div> - {% if perms.funds.delete_review or request.user == review.author %} + {% if perms.funds.delete_review or request.user == review.author.reviewer %} <div> <a class="link link--delete-review is-active" href="{% url 'apply:submissions:reviews:delete' submission_pk=object.submission.id pk=object.id %}"> Delete @@ -32,6 +32,14 @@ </a> </div> {% endif %} + {% if perms.funds.change_review or request.user == review.author.reviewer %} + <div> + <a class="link link--edit-review is-active" href="{% url 'apply:submissions:reviews:edit' submission_pk=object.submission.id pk=object.id %}"> + Edit + <svg class="icon icon--pen"><use xlink:href="#pen"></use></svg> + </a> + </div> + {% endif %} {% if not review.for_latest %} <div> <h5>Review was not against the latest version:</h5> diff --git a/opentech/apply/review/templates/review/review_edit_form.html b/opentech/apply/review/templates/review/review_edit_form.html new file mode 100644 index 0000000000000000000000000000000000000000..c027c4b2ca3c8a8c7fc2eb27ac64d85fe3ba37cc --- /dev/null +++ b/opentech/apply/review/templates/review/review_edit_form.html @@ -0,0 +1,44 @@ +{% extends "base-apply.html" %} +{% block title %}Edit a review{% endblock %} +{% block content %} +<div class="admin-bar"> + <div class="admin-bar__inner"> + <h1 class="beta heading heading--no-margin heading--bold">{{ title|default:"Edit Review" }}</h1> + <h5>For <a href="{% url "funds:submissions:detail" submission.id %}">{{ submission.title }}</a></h5> + </div> +</div> + +{% include "forms/includes/form_errors.html" with form=form %} + +<div class="wrapper wrapper--medium wrapper--inner-space-medium"> +<form class="form form--with-p-tags form--scoreable" action="" method="post" novalidate> + {{ form.media }} + {% csrf_token %} + + {% for hidden in form.hidden_fields %} + {{ hidden }} + {% endfor %} + + {% for field in form.visible_fields %} + {# to be replaced with better logic when we use stream form #} + {% ifchanged field.field.group %} + {% for key, value in form.titles.items %} + {% if key == field.field.group %} + <h2>{{ value }}</h2> + {% endif %} + {% endfor %} + {% endifchanged %} + + {% if field.field %} + {% include "forms/includes/field.html" %} + {% else %} + {{ field }} + {% endif %} + {% endfor %} + {% if not object.id or object.is_draft %} + <input class="button button--submit button--top-space button--white" type="submit" value="Save Draft" name="{{ form.draft_button_name }}" /> + {% endif %} + <input class="button button--submit button--top-space button--primary" type="submit" value="Submit" name="submit" /> +</form> +</div> +{% endblock %} diff --git a/opentech/apply/review/urls.py b/opentech/apply/review/urls.py index 2d632fb1cddd25f1ee42b697acba11c72ffe2d67..e7d575eb459f3a45cfe9f8b30c69dddc7825d5c7 100644 --- a/opentech/apply/review/urls.py +++ b/opentech/apply/review/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from .views import ReviewDetailView, ReviewListView, ReviewCreateOrUpdateView, ReviewDeleteView +from .views import ReviewDetailView, ReviewListView, ReviewCreateOrUpdateView, ReviewDeleteView, ReviewEditView app_name = 'reviews' @@ -8,5 +8,6 @@ urlpatterns = [ path('reviews/', ReviewListView.as_view(), name='list'), path('reviews/<int:pk>/', ReviewDetailView.as_view(), name="review"), path('reviews/<int:pk>/delete/', ReviewDeleteView.as_view(), name="delete"), + path('reviews/<int:pk>/edit/', ReviewEditView.as_view(), name="edit"), path('review/', ReviewCreateOrUpdateView.as_view(), name='form'), ] diff --git a/opentech/apply/review/views.py b/opentech/apply/review/views.py index 7b22550b16c81f0a9901b5f265bae3bfeb5ac861..15fe39ba7e28dad31b7826e452353713061ac48f 100644 --- a/opentech/apply/review/views.py +++ b/opentech/apply/review/views.py @@ -6,7 +6,7 @@ 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 +from django.views.generic import CreateView, ListView, DetailView, DeleteView, UpdateView from wagtail.core.blocks import RichTextBlock @@ -53,6 +53,56 @@ def get_fields_for_stage(submission): 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, + submission=review.submission, + related=review, + ) + response = super().form_valid(form) + return response + + def get_success_url(self): + review = self.get_object() + return reverse_lazy('funds:submissions:detail', args=(review.submission.id,)) + + @method_decorator(login_required, name='dispatch') class ReviewCreateOrUpdateView(BaseStreamForm, CreateOrUpdateView): submission_form_class = ReviewModelForm @@ -167,7 +217,7 @@ class ReviewDisplay(UserPassesTestMixin, DetailView): def test_func(self): review = self.get_object() user = self.request.user - author = review.author + author = review.author.reviewer submission = review.submission partner_has_access = submission.partners.filter(pk=user.pk).exists() @@ -213,7 +263,7 @@ class ReviewOpinionFormView(UserPassesTestMixin, CreateView): def test_func(self): review = self.get_object() user = self.request.user - author = review.author + author = review.author.reviewer submission = review.submission partner_has_access = submission.partners.filter(pk=user.pk).exists()