Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add submission withdrawal workflow #3298

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/setup/administrators/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,11 @@ If applicants should be forced to preview their application before submitting

----

Set the allowed file extension for all uploads fields.
### Allow Withdrawing of Submissions

ENABLE_SUBMISSION_WITHDRAWAL = env.bool('ENABLE_SUBMISSION_WITHDRAWAL', False)

### Set the allowed file extension for all uploads fields.

FILE_ALLOWED_EXTENSIONS = ['doc', 'docx', 'odp', 'ods', 'odt', 'pdf', 'ppt', 'pptx', 'rtf', 'txt', 'xls', 'xlsx']
FILE_ACCEPT_ATTR_VALUE = ', '.join(['.' + ext for ext in FILE_ALLOWED_EXTENSIONS])
Expand Down
3 changes: 2 additions & 1 deletion hypha/apply/funds/models/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -944,7 +944,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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{% extends "base-apply.html" %}
{% load i18n static %}

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

{% block content %}
<div class="admin-bar">
<div class="admin-bar__inner">
<h2 class="heading heading--no-margin">{% trans "Withdrawing" %}: {{ object.title }}</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>{% blocktrans %}Are you sure you want to withdraw "{{ object }}" from consideration?{% endblocktrans %}</strong></p>
<button class="button button--warning button--submit button--top-space" type="submit">{% trans "Confirm" %}</button>
</form>
</div>
</div>

{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ <h5>{% blocktrans with stage=object.previous.stage %}Your {{ stage }} applicatio
{% trans "Delete" %}
</a>
{% endif %}
{% if request.user|has_withdraw_perm:object %}
<a
class="font-bold text-red-600 flex items-center hover:opacity-70 transition-opacity"
href="{% url 'funds:submissions:withdraw' object.id %}">
{% heroicon_micro "arrow-uturn-down" class="inline me-1 fill-red-600" aria_hidden=true %}
bickelj marked this conversation as resolved.
Show resolved Hide resolved
{% trans "Withdraw" %}
</a>
{% endif %}
{% if request.user|has_edit_perm:object %}
<a
class="flex items-center font-bold transition-opacity hover:opacity-70"
Expand Down
7 changes: 7 additions & 0 deletions hypha/apply/funds/templatetags/workflow_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,10 @@ def display_submission_author(context: dict, revision_author: bool = False) -> s
return settings.ORG_LONG_NAME # Likely an edge case but covering bases

return str(author)


@register.filter
def has_withdraw_perm(user, submission):
if settings.ENABLE_SUBMISSION_WITHDRAWAL:
return check_permission(user, "withdraw", submission)
return False
4 changes: 4 additions & 0 deletions hypha/apply/funds/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
SubmissionResultView,
SubmissionsByStatus,
SubmissionSealedView,
SubmissionWithdrawView,
TranslateSubmissionView,
UpdateLeadView,
UpdateMetaTermsView,
Expand Down Expand Up @@ -258,6 +259,9 @@
"download/", SubmissionDetailPDFView.as_view(), name="download"
),
path("delete/", SubmissionDeleteView.as_view(), name="delete"),
path(
"withdraw/", SubmissionWithdrawView.as_view(), name="withdraw"
),
path(
"documents/<uuid:field_id>/<str:file_name>",
SubmissionPrivateMediaView.as_view(),
Expand Down
1 change: 1 addition & 0 deletions hypha/apply/funds/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def model_form_initial(instance, fields=None, exclude=None):
],
"accepted": ["accepted"],
"dismissed": ["dismissed"],
"withdrawn": ["withdrawn"],
}


Expand Down
44 changes: 42 additions & 2 deletions hypha/apply/funds/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
user_passes_test,
)
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.db.models import Count, Q
from django.forms import BaseModelForm
from django.http import (
Expand All @@ -39,7 +39,11 @@
UpdateView,
)
from django.views.generic.base import TemplateView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.detail import (
BaseDetailView,
SingleObjectMixin,
SingleObjectTemplateResponseMixin,
)
from django_file_form.models import PlaceholderUploadedFile
from django_filters.views import FilterView
from django_htmx.http import (
Expand Down Expand Up @@ -136,6 +140,7 @@
PHASES_MAPPING,
STAGE_CHANGE_ACTIONS,
active_statuses,
get_withdraw_action_for_stage,
review_statuses,
)

Expand Down Expand Up @@ -1785,6 +1790,41 @@ def form_valid(self, form):
return super().form_valid(form)


@method_decorator(login_required, name="dispatch")
class SubmissionWithdrawView(
SingleObjectTemplateResponseMixin, UserPassesTestMixin, BaseDetailView
):
model = ApplicationSubmission
success_url = reverse_lazy("funds:submissions:list")
template_name_suffix = "_confirm_withdraw"

def dispatch(self, *args, **kwargs):
self.submission = self.get_object()
return super().dispatch(*args, **kwargs)

def post(self, request, *args, **kwargs):
return self.withdraw(request, *args, **kwargs)

def withdraw(self, request, *args, **kwargs):
withdraw_action = get_withdraw_action_for_stage(self.submission.stage)

if withdraw_action:
self.submission.perform_transition(
withdraw_action, self.request.user, request=self.request, notify=False
)
else:
raise ImproperlyConfigured(
f'No withdraw actions found in workflow "{self.submission.workflow}"'
)

return HttpResponseRedirect(self.submission.get_absolute_url())

def test_func(self):
can_withdraw = self.submission.phase.permissions.can_withdraw(self.request.user)

return settings.ENABLE_SUBMISSION_WITHDRAWAL and can_withdraw


@method_decorator(login_required, name="dispatch")
class SubmissionPrivateMediaView(UserPassesTestMixin, PrivateMediaView):
raise_exception = True
Expand Down
Loading
Loading