From 71f7854b4e1ca532cda2c075e8812d81db8606dc Mon Sep 17 00:00:00 2001 From: Fredrik Jonsson Date: Wed, 7 Aug 2024 21:10:01 +0200 Subject: [PATCH] Permission fixes. --- hypha/apply/funds/views.py | 45 +++--- hypha/apply/funds/workflow.py | 268 ++++++++++++++++++++++++++++------ 2 files changed, 245 insertions(+), 68 deletions(-) diff --git a/hypha/apply/funds/views.py b/hypha/apply/funds/views.py index d45c843112..86d4bb7030 100644 --- a/hypha/apply/funds/views.py +++ b/hypha/apply/funds/views.py @@ -141,6 +141,7 @@ PHASES_MAPPING, STAGE_CHANGE_ACTIONS, active_statuses, + get_withdraw_action_for_stage, review_statuses, ) @@ -1735,43 +1736,39 @@ def form_valid(self, form): return super().form_valid(form) -class SubmissionWithdrawView(SingleObjectTemplateResponseMixin, BaseDetailView): +@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): - if not settings.ENABLE_SUBMISSION_WITHDRAWAL: - raise PermissionDenied + withdraw_action = get_withdraw_action_for_stage(self.submission.stage) - if not request.user.is_applicant: - raise PermissionDenied - - obj = self.get_object() - - withdraw_actions = [ - action for action in obj.workflow.keys() if "withdraw" in action - ] - - if len(withdraw_actions) == 1: - action = withdraw_actions[0] - obj.perform_transition( - action, self.request.user, request=self.request, notify=False - ) - elif len(withdraw_actions) > 1: - raise ImproperlyConfigured( - f'In workflow "{obj.workflow}" too many withdraw actions: "{withdraw_actions}"' + if withdraw_action: + self.submission.perform_transition( + withdraw_action, self.request.user, request=self.request, notify=False ) - elif len(withdraw_actions) < 1: + else: raise ImproperlyConfigured( - f'No withdraw actions found in workflow "{obj.workflow}"' + f'No withdraw actions found in workflow "{self.submission.workflow}"' ) - success_url = obj.get_absolute_url() - return HttpResponseRedirect(success_url) + 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") diff --git a/hypha/apply/funds/workflow.py b/hypha/apply/funds/workflow.py index 43761e2599..dbddfe85d3 100644 --- a/hypha/apply/funds/workflow.py +++ b/hypha/apply/funds/workflow.py @@ -204,7 +204,7 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): reviewer_can, partner_can, ], - "withdraw": withdraw or [applicant_can], + "withdraw": withdraw or [], } @@ -225,7 +225,7 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): ) applicant_edit_permissions = make_permissions( - edit=[applicant_can, partner_can], review=[staff_can] + edit=[applicant_can, partner_can], review=[staff_can], withdraw=[applicant_can] ) staff_edit_permissions = make_permissions(edit=[staff_can]) @@ -269,7 +269,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "almost": _("Accept but additional info required"), "accepted": _("Accept"), "rejected": _("Dismiss"), - "withdrawn": _("Withdraw"), + "withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Need screening"), "public": _("Application Received"), @@ -292,7 +296,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "almost": _("Accept but additional info required"), "accepted": _("Accept"), "rejected": _("Dismiss"), - "withdrawn": _("Withdraw"), + "withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": Request, @@ -304,7 +312,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "transitions": { "post_review_discussion": _("Close Review"), INITIAL_STATE: _("Need screening (revert)"), - "withdrawn": _("Withdraw"), + "withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Internal Review"), "public": _("{org_short_name} Review").format( @@ -323,7 +335,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "almost": _("Accept but additional info required"), "accepted": _("Accept"), "rejected": _("Dismiss"), - "withdrawn": _("Withdraw"), + "withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready For Discussion"), "stage": Request, @@ -345,7 +361,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "almost": _("Accept but additional info required"), "accepted": _("Accept"), "rejected": _("Dismiss"), - "withdrawn": _("Withdraw"), + "withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": Request, @@ -359,7 +379,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "almost": _("Accept but additional info required"), "accepted": _("Accept"), "rejected": _("Dismiss"), - "withdrawn": _("Withdraw"), + "withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready for Determination"), "permissions": hidden_from_applicant_permissions, @@ -377,7 +401,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "transitions": { "accepted": _("Accept"), "post_review_discussion": _("Ready For Discussion (revert)"), - "withdrawn": _("Withdraw"), + "withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Accepted but additional info required"), "stage": Request, @@ -418,7 +446,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "ext_internal_review": _("Open Review"), "ext_determination": _("Ready For Determination"), "ext_rejected": _("Dismiss"), - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Need screening"), "public": _("Application Received"), @@ -437,7 +469,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): }, "method": "create_revision", }, - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": RequestExt, @@ -448,7 +484,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "ext_internal_review": { "transitions": { "ext_post_review_discussion": _("Close Review"), - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, INITIAL_STATE: _("Need screening (revert)"), }, "display": _("Internal Review"), @@ -467,7 +507,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "ext_determination": _("Ready For Determination"), "ext_internal_review": _("Open Internal Review (revert)"), "ext_rejected": _("Dismiss"), - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready For Discussion"), "stage": RequestExt, @@ -485,7 +529,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): }, "method": "create_revision", }, - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": RequestExt, @@ -512,7 +560,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "ext_almost": _("Accept but additional info required"), "ext_accepted": _("Accept"), "ext_rejected": _("Dismiss"), - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready For Discussion"), "stage": RequestExt, @@ -530,7 +582,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): }, "method": "create_revision", }, - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": RequestExt, @@ -546,7 +602,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "ext_almost": _("Accept but additional info required"), "ext_accepted": _("Accept"), "ext_rejected": _("Dismiss"), - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready for Determination"), "permissions": hidden_from_applicant_permissions, @@ -566,7 +626,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "ext_post_external_review_discussion": _( "Ready For Discussion (revert)" ), - "ext_withdrawn": _("Withdraw"), + "ext_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Accepted but additional info required"), "stage": RequestExt, @@ -610,7 +674,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "com_community_review": _("Open Community Review"), "com_determination": _("Ready For Determination"), "com_rejected": _("Dismiss"), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Need screening"), "public": _("Application Received"), @@ -638,7 +706,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "transitions": { INITIAL_STATE: _("Need screening (revert)"), "com_rejected": _("Dismiss"), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": "Open Call (public)", "stage": RequestCom, @@ -652,7 +724,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "com_post_review_discussion": _("Close Review"), INITIAL_STATE: _("Need screening (revert)"), "com_rejected": _("Dismiss"), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Internal Review"), "public": _("{org_short_name} Review").format( @@ -666,7 +742,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "com_post_review_discussion": _("Close Review"), "com_internal_review": _("Open Internal Review (revert)"), "com_rejected": _("Dismiss"), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Community Review"), "public": _("{org_short_name} Review").format( @@ -684,7 +764,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "com_determination": _("Ready For Determination"), "com_internal_review": _("Open Internal Review (revert)"), "com_rejected": _("Dismiss"), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready For Discussion"), "stage": RequestCom, @@ -702,7 +786,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): }, "method": "create_revision", }, - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": RequestCom, @@ -714,7 +802,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "transitions": { "com_post_external_review_discussion": _("Close Review"), "com_post_review_discussion": _("Ready For Discussion (revert)"), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("External Review"), "stage": RequestCom, @@ -730,7 +822,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "com_almost": _("Accept but additional info required"), "com_accepted": _("Accept"), "com_rejected": _("Dismiss"), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready For Discussion"), "stage": RequestCom, @@ -748,7 +844,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): }, "method": "create_revision", }, - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": RequestCom, @@ -764,7 +864,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "com_almost": _("Accept but additional info required"), "com_accepted": _("Accept"), "com_rejected": _("Dismiss"), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready for Determination"), "permissions": hidden_from_applicant_permissions, @@ -784,7 +888,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "com_post_external_review_discussion": _( "Ready For Discussion (revert)" ), - "com_withdrawn": _("Withdraw"), + "com_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Accepted but additional info required"), "stage": RequestCom, @@ -827,7 +935,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "concept_determination": _("Ready For Preliminary Determination"), "invited_to_proposal": _("Invite to Proposal"), "concept_rejected": _("Dismiss"), - "concept_withdrawn": _("Withdraw"), + "concept_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Need screening"), "public": _("Concept Note Received"), @@ -849,7 +961,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "concept_rejected": _("Dismiss"), "invited_to_proposal": _("Invite to Proposal"), "concept_determination": _("Ready For Preliminary Determination"), - "concept_withdrawn": _("Withdraw"), + "concept_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": Concept, @@ -862,7 +978,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "concept_review_discussion": _("Close Review"), INITIAL_STATE: _("Need screening (revert)"), "invited_to_proposal": _("Invite to Proposal"), - "concept_withdrawn": _("Withdraw"), + "concept_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Internal Review"), "public": _("{org_short_name} Review").format( @@ -880,7 +1000,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "concept_internal_review": _("Open Review (revert)"), "invited_to_proposal": _("Invite to Proposal"), "concept_rejected": _("Dismiss"), - "concept_withdrawn": _("Withdraw"), + "concept_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready For Discussion"), "stage": Concept, @@ -899,7 +1023,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "method": "create_revision", }, "invited_to_proposal": _("Invite to Proposal"), - "concept_withdrawn": _("Withdraw"), + "concept_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": Concept, @@ -912,7 +1040,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "concept_review_discussion": _("Ready For Discussion (revert)"), "invited_to_proposal": _("Invite to Proposal"), "concept_rejected": _("Dismiss"), - "concept_withdrawn": _("Withdraw"), + "concept_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready for Preliminary Determination"), "permissions": hidden_from_applicant_permissions, @@ -974,7 +1106,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "external_review": _("Open External Review"), "proposal_determination": _("Ready For Final Determination"), "proposal_rejected": _("Dismiss"), - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Proposal Received"), "stage": Proposal, @@ -995,7 +1131,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "external_review": _("Open External Review"), "proposal_determination": _("Ready For Final Determination"), "proposal_rejected": _("Dismiss"), - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": Proposal, @@ -1007,7 +1147,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "transitions": { "post_proposal_review_discussion": _("Close Review"), "proposal_discussion": _("Proposal Received (revert)"), - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Internal Review"), "public": _("{org_short_name} Review").format( @@ -1025,7 +1169,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "proposal_determination": _("Ready For Final Determination"), "proposal_internal_review": _("Open Internal Review (revert)"), "proposal_rejected": _("Dismiss"), - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready For Discussion"), "stage": Proposal, @@ -1044,7 +1192,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "method": "create_revision", }, "external_review": _("Open External Review"), - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": Proposal, @@ -1056,7 +1208,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "transitions": { "post_external_review_discussion": _("Close Review"), "post_proposal_review_discussion": _("Ready For Discussion (revert)"), - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("External Review"), "stage": Proposal, @@ -1072,7 +1228,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "proposal_almost": _("Accept but additional info required"), "proposal_accepted": _("Accept"), "proposal_rejected": _("Dismiss"), - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready For Discussion"), "stage": Proposal, @@ -1090,7 +1250,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): }, "method": "create_revision", }, - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("More information required"), "stage": Proposal, @@ -1104,7 +1268,11 @@ def make_permissions(edit=None, review=None, view=None, withdraw=None): "proposal_almost": _("Accept but additional info required"), "proposal_accepted": _("Accept"), "proposal_rejected": _("Dismiss"), - "proposal_withdrawn": _("Withdraw"), + "proposal_withdrawn": { + "display": _("Withdraw"), + "permissions": {UserPermissions.APPLICANT}, + "method": "withdraw", + }, }, "display": _("Ready for Final Determination"), "permissions": hidden_from_applicant_permissions, @@ -1272,6 +1440,18 @@ def get_ext_or_higher_statuses(): return reviews +def get_withdraw_action_for_stage(stage=None): + """ + Returns the withdraw action for stage. + """ + for workflow in WORKFLOWS.values(): + for phase in workflow.values(): + if phase.stage == stage and phase.name in withdrawn_statuses: + return phase.name + + return False + + def get_accepted_statuses(): accepted_statuses = set() for phase_name, phase in PHASES: