diff --git a/hypha/apply/activity/adapters/activity_feed.py b/hypha/apply/activity/adapters/activity_feed.py index 6613dcf8a8631105c6641ca4adde8ccf541d6cb2..e39c079b8996fee2ecbc03b79e86e172b1446201 100644 --- a/hypha/apply/activity/adapters/activity_feed.py +++ b/hypha/apply/activity/adapters/activity_feed.py @@ -41,6 +41,9 @@ class ActivityAdapter(AdapterBase): MESSAGES.REVIEW_OPINION: _( "{user} {opinion.opinion_display}s with {opinion.review.author}s review of {source}" ), + MESSAGES.DELETE_REVIEW_OPINION: _( + "{user} deleted the opinion for review: {review_opinion.review}" + ), MESSAGES.CREATED_PROJECT: _("Created"), MESSAGES.PROJECT_TRANSITION: "handle_project_transition", MESSAGES.UPDATE_PROJECT_LEAD: _( @@ -79,6 +82,7 @@ class ActivityAdapter(AdapterBase): MESSAGES.REVIEWERS_UPDATED, MESSAGES.SCREENING, MESSAGES.REVIEW_OPINION, + MESSAGES.DELETE_REVIEW_OPINION, MESSAGES.BATCH_REVIEWERS_UPDATED, MESSAGES.PARTNERS_UPDATED, MESSAGES.APPROVE_PROJECT, diff --git a/hypha/apply/activity/adapters/base.py b/hypha/apply/activity/adapters/base.py index f8263851b3b7cae339bb77ce99667e48159cee43..894b880daf18722777452772dd94701f0c47f7d1 100644 --- a/hypha/apply/activity/adapters/base.py +++ b/hypha/apply/activity/adapters/base.py @@ -16,6 +16,7 @@ neat_related = { MESSAGES.SCREENING: "old_status", MESSAGES.REVIEW_OPINION: "opinion", MESSAGES.DELETE_REVIEW: "review", + MESSAGES.DELETE_REVIEW_OPINION: "review_opinion", MESSAGES.EDIT_REVIEW: "review", MESSAGES.CREATED_PROJECT: "submission", MESSAGES.PROJECT_TRANSITION: "old_stage", diff --git a/hypha/apply/activity/adapters/slack.py b/hypha/apply/activity/adapters/slack.py index 5925265e966a5c3b61c548ffd93e37f607e44eba..c19b457adedce131b80bb1dfaf58d3d690bff30d 100644 --- a/hypha/apply/activity/adapters/slack.py +++ b/hypha/apply/activity/adapters/slack.py @@ -73,6 +73,9 @@ class SlackAdapter(AdapterBase): MESSAGES.DELETE_REVIEW: _( "{user} has deleted {review.author} review for <{link}|{source.title}>" ), + MESSAGES.DELETE_REVIEW_OPINION: _( + "{user} has deleted {review_opinion.author} review opinion for <{link}|{source.title}>" + ), MESSAGES.CREATED_PROJECT: _( "{user} has created a Project: <{link}|{source.title}>" ), diff --git a/hypha/apply/activity/migrations/0076_alter_event_type.py b/hypha/apply/activity/migrations/0076_alter_event_type.py new file mode 100644 index 0000000000000000000000000000000000000000..84e9a723a4104683cb79308af8badd6e528bede1 --- /dev/null +++ b/hypha/apply/activity/migrations/0076_alter_event_type.py @@ -0,0 +1,83 @@ +# Generated by Django 3.2.20 on 2023-09-09 07:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("activity", "0075_alter_activity_visibility"), + ] + + operations = [ + migrations.AlterField( + model_name="event", + name="type", + field=models.CharField( + choices=[ + ("UPDATE_LEAD", "updated lead"), + ("BATCH_UPDATE_LEAD", "batch updated lead"), + ("EDIT_SUBMISSION", "edited submission"), + ("APPLICANT_EDIT", "edited applicant"), + ("NEW_SUBMISSION", "submitted new submission"), + ("DRAFT_SUBMISSION", "submitted new draft submission"), + ("SCREENING", "screened"), + ("TRANSITION", "transitioned"), + ("BATCH_TRANSITION", "batch transitioned"), + ("DETERMINATION_OUTCOME", "sent determination outcome"), + ("BATCH_DETERMINATION_OUTCOME", "sent batch determination outcome"), + ("INVITED_TO_PROPOSAL", "invited to proposal"), + ("REVIEWERS_UPDATED", "updated reviewers"), + ("BATCH_REVIEWERS_UPDATED", "batch updated reviewers"), + ("PARTNERS_UPDATED", "updated partners"), + ("PARTNERS_UPDATED_PARTNER", "partners updated partner"), + ("READY_FOR_REVIEW", "marked ready for review"), + ("BATCH_READY_FOR_REVIEW", "marked batch ready for review"), + ("NEW_REVIEW", "added new review"), + ("COMMENT", "added comment"), + ("PROPOSAL_SUBMITTED", "submitted proposal"), + ("OPENED_SEALED", "opened sealed submission"), + ("REVIEW_OPINION", "reviewed opinion"), + ("DELETE_SUBMISSION", "deleted submission"), + ("DELETE_REVIEW", "deleted review"), + ("DELETE_REVIEW_OPINION", "deleted review opinion"), + ("CREATED_PROJECT", "created project"), + ("UPDATED_VENDOR", "updated contracting information"), + ("UPDATE_PROJECT_LEAD", "updated project lead"), + ("EDIT_REVIEW", "edited review"), + ("SEND_FOR_APPROVAL", "sent for approval"), + ("APPROVE_PROJECT", "approved project"), + ("ASSIGN_PAF_APPROVER", "assign paf approver"), + ("APPROVE_PAF", "approved paf"), + ("PROJECT_TRANSITION", "transitioned project"), + ("REQUEST_PROJECT_CHANGE", "requested project change"), + ("SUBMIT_CONTRACT_DOCUMENTS", "submitted contract documents"), + ("UPLOAD_DOCUMENT", "uploaded document to project"), + ("REMOVE_DOCUMENT", "removed document from project"), + ("UPLOAD_CONTRACT", "uploaded contract to project"), + ("APPROVE_CONTRACT", "approved contract"), + ("CREATE_INVOICE", "created invoice for project"), + ("UPDATE_INVOICE_STATUS", "updated invoice status"), + ("APPROVE_INVOICE", "approve invoice"), + ("DELETE_INVOICE", "deleted invoice"), + ("SENT_TO_COMPLIANCE", "sent project to compliance"), + ("UPDATE_INVOICE", "updated invoice"), + ("SUBMIT_REPORT", "submitted report"), + ("SKIPPED_REPORT", "skipped report"), + ("REPORT_FREQUENCY_CHANGED", "changed report frequency"), + ("DISABLED_REPORTING", "disabled reporting"), + ("REPORT_NOTIFY", "notified report"), + ("CREATE_REMINDER", "created reminder"), + ("DELETE_REMINDER", "deleted reminder"), + ("REVIEW_REMINDER", "reminder to review"), + ("BATCH_DELETE_SUBMISSION", "batch deleted submissions"), + ("BATCH_ARCHIVE_SUBMISSION", "batch archive submissions"), + ("STAFF_ACCOUNT_CREATED", "created new account"), + ("STAFF_ACCOUNT_EDITED", "edited account"), + ("ARCHIVE_SUBMISSION", "archived submission"), + ("UNARCHIVE_SUBMISSION", "unarchived submission"), + ], + max_length=50, + verbose_name="verb", + ), + ), + ] diff --git a/hypha/apply/activity/options.py b/hypha/apply/activity/options.py index bb3861fa7ed90ae0ac1f825a1ef8c11be1dcabb4..117727d85734e3a18584fd615a3a83d8dffa9716 100644 --- a/hypha/apply/activity/options.py +++ b/hypha/apply/activity/options.py @@ -33,6 +33,7 @@ class MESSAGES(TextChoices): REVIEW_OPINION = "REVIEW_OPINION", _("reviewed opinion") DELETE_SUBMISSION = "DELETE_SUBMISSION", _("deleted submission") DELETE_REVIEW = "DELETE_REVIEW", _("deleted review") + DELETE_REVIEW_OPINION = "DELETE_REVIEW_OPINION", _("deleted review opinion") CREATED_PROJECT = "CREATED_PROJECT", _("created project") UPDATED_VENDOR = "UPDATED_VENDOR", _("updated contracting information") UPDATE_PROJECT_LEAD = "UPDATE_PROJECT_LEAD", _("updated project lead") diff --git a/hypha/apply/funds/templates/funds/includes/review_sidebar_item.html b/hypha/apply/funds/templates/funds/includes/review_sidebar_item.html index 9aaf018142714f18d0b78668f9179ddb0f786a36..11eb7c0fbc512686d177a5f685d563aca1b1918c 100644 --- a/hypha/apply/funds/templates/funds/includes/review_sidebar_item.html +++ b/hypha/apply/funds/templates/funds/includes/review_sidebar_item.html @@ -1,4 +1,4 @@ -{% load wagtailimages_tags %} +{% load wagtailimages_tags heroicons %} <li class="reviews-sidebar__item {% if not reviewer.review %}no-response {% endif %}" @@ -48,6 +48,9 @@ </div> <div></div> <div class="reviews-sidebar__outcome {{ opinion.get_opinion_display|slugify }}">{{ opinion.get_opinion_display}}</div> + <div class="text-center"><a href="{% url 'apply:submissions:reviews:delete_opinion' submission_pk=opinion.review.submission.id pk=opinion.id %}"> + {% heroicon_outline "trash" aria_hidden="true" size=14 class="stroke-red-800 align-middle inline" %}</a> + </div> </li> {% if forloop.last %} </ul> diff --git a/hypha/apply/review/models.py b/hypha/apply/review/models.py index 19dc4ff3aea5e8d8e77d00b4d36fc20463e6d885..ec1b1bd1667b425d6b7d80555127950d253b5989 100644 --- a/hypha/apply/review/models.py +++ b/hypha/apply/review/models.py @@ -248,6 +248,9 @@ class ReviewOpinion(models.Model): class Meta: unique_together = ("author", "review") + def __str__(self): + return f"Review Opinion for {self.review}" + @property def opinion_display(self): return self.get_opinion_display() diff --git a/hypha/apply/review/templates/review/reviewopinion_confirm_delete.html b/hypha/apply/review/templates/review/reviewopinion_confirm_delete.html new file mode 100644 index 0000000000000000000000000000000000000000..21238b428002c3a7369f43027bbbb54dae56fe34 --- /dev/null +++ b/hypha/apply/review/templates/review/reviewopinion_confirm_delete.html @@ -0,0 +1,22 @@ +{% extends "base-apply.html" %} +{% load i18n %} + +{% block title %}{% trans "Deleting" %}: {{ object }}{% endblock %} + +{% block content %} + <div class="admin-bar"> + <div class="admin-bar__inner"> + <h2 class="heading heading--no-margin">{% trans "Deleting" %}: {{ object }}</h2> + </div> + </div> + + <div class="wrapper wrapper--light-grey-bg wrapper--form wrapper--sidebar"> + <div class="wrapper--sidebar--inner"> + <form class="form" action="" method="post"> + {% csrf_token %} + <p><strong>{% trans "Are you sure you want to delete" %} "{{ object }}"?</strong></p> + <button class="button button--warning button--submit button--top-space" type="submit">{% trans "Confirm" %}</button> + </form> + </div> + </div> +{% endblock %} diff --git a/hypha/apply/review/urls.py b/hypha/apply/review/urls.py index 9c0dc1e41e84d18513018085a289e5780a4a88a2..c5258be782cc370fc106a166c347e82024091b41 100644 --- a/hypha/apply/review/urls.py +++ b/hypha/apply/review/urls.py @@ -6,6 +6,7 @@ from .views import ( ReviewDetailView, ReviewEditView, ReviewListView, + ReviewOpinionDeleteView, ) app_name = "reviews" @@ -16,4 +17,9 @@ urlpatterns = [ 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"), + path( + "review/opinion/<int:pk>/delete/", + ReviewOpinionDeleteView.as_view(), + name="delete_opinion", + ), ] diff --git a/hypha/apply/review/views.py b/hypha/apply/review/views.py index fe10489a992d8f375fac82f2ce380fd41ae153b1..c098c0a69d884ebb6dc1203955db71e36e612d66 100644 --- a/hypha/apply/review/views.py +++ b/hypha/apply/review/views.py @@ -27,7 +27,7 @@ from hypha.apply.users.decorators import staff_required from hypha.apply.utils.image import generate_image_tag from hypha.apply.utils.views import CreateOrUpdateView -from .models import Review +from .models import Review, ReviewOpinion from .options import DISAGREE @@ -490,3 +490,30 @@ class ReviewDeleteView(UserPassesTestMixin, DeleteView): 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 ReviewOpinionDeleteView(DeleteView): + model = ReviewOpinion + raise_exception = True + + def dispatch(self, request, *args, **kwargs): + self.review_opinion = self.get_object() + if self.request.user != self.review_opinion.author.reviewer: + raise PermissionDenied + return super().dispatch(request, *args, **kwargs) + + def delete(self, request, *args, **kwargs): + messenger( + MESSAGES.DELETE_REVIEW_OPINION, + user=request.user, + request=request, + source=self.review_opinion.review.submission, + related=self.review_opinion, + ) + response = super().delete(request, *args, **kwargs) + return response + + def get_success_url(self): + review = self.review_opinion.review + return reverse_lazy("funds:submissions:detail", args=(review.submission.id,))