From 9faa366d672eb165d9e896e5aae0b9195d9b3110 Mon Sep 17 00:00:00 2001 From: Chris Zubak-Skees Date: Tue, 22 Feb 2022 03:30:33 -0500 Subject: [PATCH] feat: add submission withdrawal Closes #3296 --- hypha/apply/funds/models/submissions.py | 3 +- ...pplicationsubmission_confirm_withdraw.html | 23 ++++++++++ .../funds/applicationsubmission_detail.html | 7 +++ hypha/apply/funds/urls.py | 2 + hypha/apply/funds/views.py | 33 +++++++++++++- hypha/apply/funds/workflow.py | 43 ++++++++++++++++++- .../src/sass/apply/custom/_custom.scss | 7 ++- 7 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 hypha/apply/funds/templates/funds/applicationsubmission_confirm_withdraw.html diff --git a/hypha/apply/funds/models/submissions.py b/hypha/apply/funds/models/submissions.py index cbd5e70875..6989488bf1 100644 --- a/hypha/apply/funds/models/submissions.py +++ b/hypha/apply/funds/models/submissions.py @@ -834,7 +834,8 @@ def in_external_review_phase(self): def is_finished(self): accepted = self.status in PHASES_MAPPING['accepted']['statuses'] dismissed = self.status in PHASES_MAPPING['dismissed']['statuses'] - return accepted or dismissed + withdrawn = self.status in PHASES_MAPPING['withdrawn']['statuses'] + return accepted or dismissed or withdrawn # Methods for accessing data on the submission diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_confirm_withdraw.html b/hypha/apply/funds/templates/funds/applicationsubmission_confirm_withdraw.html new file mode 100644 index 0000000000..d0830b01b6 --- /dev/null +++ b/hypha/apply/funds/templates/funds/applicationsubmission_confirm_withdraw.html @@ -0,0 +1,23 @@ +{% extends "base-apply.html" %} +{% load i18n static %} + +{% block title %}{% trans "Withdrawing" %}: {{object.title }}{% endblock %} + +{% block content %} +
+
+

{% trans "Withdrawing" %}: {{ object.title }}

+
+
+ +
+
+
+ {% csrf_token %} +

{% blocktrans %}Are you sure you want to withdraw "{{ object }}" from consideration?{% endblocktrans %}

+ +
+
+
+ +{% endblock %} diff --git a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html index cba7ed5958..34a5efc8b7 100644 --- a/hypha/apply/funds/templates/funds/applicationsubmission_detail.html +++ b/hypha/apply/funds/templates/funds/applicationsubmission_detail.html @@ -81,6 +81,13 @@
{% blocktrans with stage=object.previous.stage %}Your {{ stage }} applicatio {% endif %} + {% if request.user|has_edit_perm:object %} + {% if request.user.is_applicant %} + + {% trans "Withdraw" %} + + {% endif %} + {% endif %} {% if request.user|has_edit_perm:object %} {% trans "Edit" %} diff --git a/hypha/apply/funds/urls.py b/hypha/apply/funds/urls.py index 0f4e7a03e8..2a8b1c773c 100644 --- a/hypha/apply/funds/urls.py +++ b/hypha/apply/funds/urls.py @@ -13,6 +13,7 @@ RoundListView, StaffAssignments, SubmissionDeleteView, + SubmissionWithdrawView, SubmissionDetailPDFView, SubmissionDetailSimplifiedView, SubmissionDetailView, @@ -64,6 +65,7 @@ path('simplified/', SubmissionDetailSimplifiedView.as_view(), name="simplified"), path('download/', SubmissionDetailPDFView.as_view(), name="download"), path('delete/', SubmissionDeleteView.as_view(), name="delete"), + path('withdraw/', SubmissionWithdrawView.as_view(), name="withdraw"), path( 'documents//', SubmissionPrivateMediaView.as_view(), name='serve_private_media' diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py index ed3207ab66..f4a2c912c1 100644 --- a/hypha/apply/funds/views.py +++ b/hypha/apply/funds/views.py @@ -28,7 +28,11 @@ UpdateView, ) from django.views.generic.base import TemplateView -from django.views.generic.detail import SingleObjectMixin +from django.views.generic.detail import ( + SingleObjectMixin, + BaseDetailView, + SingleObjectTemplateResponseMixin, +) from django_file_form.models import PlaceholderUploadedFile from django_filters.views import FilterView from django_tables2.paginators import LazyPaginator @@ -1392,6 +1396,33 @@ def delete(self, request, *args, **kwargs): response = super().delete(request, *args, **kwargs) return response +class SubmissionWithdrawView(SingleObjectTemplateResponseMixin, BaseDetailView): + model = ApplicationSubmission + success_url = reverse_lazy('funds:submissions:list') + template_name_suffix = '_confirm_withdraw' + + def post(self, request, *args, **kwargs): + return self.withdraw(request, *args, **kwargs) + + def withdraw(self, request, *args, **kwargs): + obj = self.get_object() + + if not obj.phase.permissions.can_edit(request.user): + raise PermissionDenied + + withdraw_actions = [action for action in obj.workflow[obj.status].transitions.keys() if 'withdraw' in action] + + if len(withdraw_actions) > 0: + action = withdraw_actions[0] + obj.perform_transition( + action, + self.request.user, + request=self.request, + notify=False + ) + + success_url = obj.get_absolute_url() + return HttpResponseRedirect(success_url) @method_decorator(login_required, name='dispatch') class SubmissionPrivateMediaView(UserPassesTestMixin, PrivateMediaView): diff --git a/hypha/apply/funds/workflow.py b/hypha/apply/funds/workflow.py index a3f39e3dfd..cae8c55009 100644 --- a/hypha/apply/funds/workflow.py +++ b/hypha/apply/funds/workflow.py @@ -343,6 +343,7 @@ def make_permissions(edit=None, review=None, view=None): 'ext_internal_review': _('Open Review'), 'ext_determination': _('Ready For Determination'), 'ext_rejected': _('Dismiss'), + 'ext_screening_withdrawn': _('Withdraw'), }, 'display': _('Need screening'), 'public': _('Application Received'), @@ -356,16 +357,23 @@ def make_permissions(edit=None, review=None, view=None): 'permissions': {UserPermissions.APPLICANT, UserPermissions.STAFF, UserPermissions.LEAD, UserPermissions.ADMIN}, 'method': 'create_revision', }, + 'ext_screening_withdrawn': _('Withdraw'), }, 'display': _('More information required'), 'stage': RequestExt, 'permissions': applicant_edit_permissions, }, + 'ext_screening_withdrawn': { + 'display': _('Withdrawn'), + 'stage': RequestExt, + 'permissions': staff_edit_permissions, + }, }, { 'ext_internal_review': { 'transitions': { 'ext_post_review_discussion': _('Close Review'), + 'ext_review_withdrawn': _('Withdraw'), INITIAL_STATE: _('Need screening (revert)'), }, 'display': _('Internal Review'), @@ -394,6 +402,7 @@ def make_permissions(edit=None, review=None, view=None): 'permissions': {UserPermissions.APPLICANT, UserPermissions.STAFF, UserPermissions.LEAD, UserPermissions.ADMIN}, 'method': 'create_revision', }, + 'ext_review_withdrawn': _('Withdraw'), }, 'display': _('More information required'), 'stage': RequestExt, @@ -410,6 +419,11 @@ def make_permissions(edit=None, review=None, view=None): 'stage': RequestExt, 'permissions': reviewer_review_permissions, }, + 'ext_review_withdrawn': { + 'display': _('Withdrawn'), + 'stage': RequestExt, + 'permissions': staff_edit_permissions, + }, }, { 'ext_post_external_review_discussion': { @@ -420,6 +434,7 @@ def make_permissions(edit=None, review=None, view=None): 'ext_almost': _('Accept but additional info required'), 'ext_accepted': _('Accept'), 'ext_rejected': _('Dismiss'), + 'ext_external_review_withdrawn': _('Withdraw'), }, 'display': _('Ready For Discussion'), 'stage': RequestExt, @@ -432,11 +447,17 @@ def make_permissions(edit=None, review=None, view=None): 'permissions': {UserPermissions.APPLICANT, UserPermissions.STAFF, UserPermissions.LEAD, UserPermissions.ADMIN}, 'method': 'create_revision', }, + 'ext_external_review_withdrawn': _('Withdraw'), }, 'display': _('More information required'), 'stage': RequestExt, 'permissions': applicant_edit_permissions, }, + 'ext_external_review_withdrawn': { + 'display': _('Withdrawn'), + 'stage': RequestExt, + 'permissions': staff_edit_permissions, + }, }, { 'ext_determination': { @@ -445,6 +466,7 @@ def make_permissions(edit=None, review=None, view=None): 'ext_almost': _('Accept but additional info required'), 'ext_accepted': _('Accept'), 'ext_rejected': _('Dismiss'), + 'ext_withdrawn': _('Withdraw'), }, 'display': _('Ready for Determination'), 'permissions': hidden_from_applicant_permissions, @@ -470,7 +492,12 @@ def make_permissions(edit=None, review=None, view=None): 'ext_rejected': { 'display': _('Dismissed'), 'stage': RequestExt, - 'permissions': no_permissions, + 'permissions': staff_edit_permissions, + }, + 'ext_withdrawn': { + 'display': _('Withdrawn'), + 'stage': RequestExt, + 'permissions': staff_edit_permissions, }, }, ] @@ -1061,11 +1088,21 @@ def get_dismissed_statuses(): return dismissed_statuses +def get_withdrawn_statuses(): + withdrawn_statuses = set() + for phase_name, phase in PHASES: + if phase.display_name == 'Dismissed': + withdrawn_statuses.add(phase_name) + return withdrawn_statuses + + +ext_or_higher_statuses = get_ext_or_higher_statuses() review_statuses = get_review_statuses() ext_review_statuses = get_ext_review_statuses() ext_or_higher_statuses = get_ext_or_higher_statuses() accepted_statuses = get_accepted_statuses() dismissed_statuses = get_dismissed_statuses() +withdrawn_statuses = get_withdrawn_statuses() DETERMINATION_PHASES = [phase_name for phase_name, _ in PHASES if '_discussion' in phase_name] DETERMINATION_RESPONSE_PHASES = [ @@ -1159,6 +1196,10 @@ def phases_matching(phrase, exclude=None): 'name': _('Dismissed'), 'statuses': phases_matching('rejected'), }, + 'withdrawn': { + 'name': _('Withdrawn'), + 'statuses': phases_matching('withdrawn'), + }, } OPEN_CALL_PHASES = [ diff --git a/hypha/static_src/src/sass/apply/custom/_custom.scss b/hypha/static_src/src/sass/apply/custom/_custom.scss index 061b3d43d2..cc17f79c15 100644 --- a/hypha/static_src/src/sass/apply/custom/_custom.scss +++ b/hypha/static_src/src/sass/apply/custom/_custom.scss @@ -1 +1,6 @@ -// stylelint-disable no-empty-source +.link--withdraw-submission { + margin-right: 1rem; + padding-right: 1rem; + border-right: 2px solid #cfcfcf; + font-weight: 700; +}