Skip to content

Commit

Permalink
Update delete submission to use role-permissions module
Browse files Browse the repository at this point in the history
  • Loading branch information
theskumar committed Nov 4, 2024
1 parent 0adf965 commit 8239c21
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{% load i18n dashboard_statusbar_tags statusbar_tags workflow_tags heroicons submission_tags %}
{% load can from permission_tags %}

{% for submission in page.object_list %}
<div class="wrapper wrapper--status-bar-outer">
Expand Down Expand Up @@ -28,7 +29,7 @@ <h4 class="heading mb-0 font-bold line-clamp-3 hover:line-clamp-none">
{% endif %}
</a>
{% endif %}
{% user_can_delete_submission submission request.user as can_delete_submission %}
{% can "delete_submission" submission as can_delete_submission %}
{% if can_delete_submission %}
<a class="button button--white" href="{% url 'funds:submissions:delete' submission.id %}" hx-get="{% url 'funds:submissions:delete' submission.id %}" hx-target="#htmx-modal">
{% heroicon_micro "trash" class="inline me-1 mt-1 fill-red-600" aria_hidden=true %}
Expand Down
29 changes: 22 additions & 7 deletions hypha/apply/funds/permissions.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from django.conf import settings
from django.core.exceptions import PermissionDenied
from rolepermissions.permissions import register_object_checker

from hypha.apply.funds.models.submissions import DRAFT_STATE

from ..users.roles import STAFF_GROUP_NAME, SUPERADMIN, TEAMADMIN_GROUP_NAME
from ..users.roles import STAFF_GROUP_NAME, SUPERADMIN, TEAMADMIN_GROUP_NAME, StaffAdmin


def has_permission(action, user, object=None, raise_exception=True):
Expand All @@ -26,12 +27,27 @@ def can_edit_submission(user, submission):
return True, ""


def can_delete_submission(user, submission):
@register_object_checker()
def delete_submission(role, user, submission) -> bool:
"""
Determines if a user has permission to delete a submission.
Permissions are granted if:
- User is a Superuser, or StaffAdmin
- User has explicit delete_applicationsubmission permission
- User is the applicant of the submission and it is in draft state
"""
if role == StaffAdmin:
return True

if user.has_perm("funds.delete_applicationsubmission"):
return True, "User can delete submission"
elif user == submission.user and submission.status == DRAFT_STATE:
return True, "Applicant can delete draft submissions"
return False, "Forbidden Error"
return True

# Allow the user to delete their own draft submissions
if user == submission.user and submission.status == DRAFT_STATE:
return True

return False


def can_bulk_delete_submissions(user) -> bool:
Expand Down Expand Up @@ -174,7 +190,6 @@ def can_view_submission_screening(user, submission):
permissions_map = {
"submission_view": is_user_has_access_to_view_submission,
"submission_edit": can_edit_submission,
"submission_delete": can_delete_submission,
"can_view_submission_screening": can_view_submission_screening,
"archive_alter": can_alter_archived_submissions,
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{% extends "base-apply.html" %}
{% load i18n static workflow_tags wagtailcore_tags statusbar_tags archive_tags submission_tags %}
{% load heroicons %}
{% load can from permission_tags %}

{% block title %}#{{ object.public_id|default_if_none:object.id}}: {{ object.title }}{% endblock %}
{% block body_class %}{% endblock %}
Expand Down Expand Up @@ -120,7 +121,7 @@ <h5>{% blocktrans with stage=object.previous.stage %}Your {{ stage }} applicatio
</strong>
</span>
<div class="flex gap-4 justify-end flex-1 items-center">
{% user_can_delete_submission object request.user as can_delete_submission %}
{% can "delete_submission" object as can_delete_submission %}
{% if can_delete_submission %}
<a
class="flex items-center font-bold text-red-600 transition-opacity hover:opacity-70"
Expand Down
12 changes: 0 additions & 12 deletions hypha/apply/funds/templatetags/submission_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from django.utils.safestring import mark_safe

from hypha.apply.funds.models import ApplicationSubmission
from hypha.apply.funds.permissions import has_permission

register = template.Library()

Expand All @@ -30,14 +29,3 @@ def submission_links(value):
value = re.sub(rf"(?<!\w){sid}(?!\w)", link, value)

return mark_safe(value)


@register.simple_tag
def user_can_delete_submission(submission, user):
permission, _ = has_permission(
"submission_delete",
user=user,
object=submission,
raise_exception=False,
)
return permission
62 changes: 62 additions & 0 deletions hypha/apply/funds/tests/views/test_submission_delete.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from django.urls import reverse

from hypha.apply.funds.tests.factories.models import ApplicationSubmissionFactory
from hypha.apply.funds.workflow import DRAFT_STATE
from hypha.apply.users.tests.factories import AdminFactory, ApplicantFactory


def test_delete_submission_view_login(db, client):
submission = ApplicationSubmissionFactory(status="internal_review")
delete_url = reverse("apply:submissions:delete", kwargs={"pk": submission.pk})
res = client.get(delete_url)

# check login required
assert res.status_code == 302
assert f"/auth/?next={delete_url}" in res.url


def test_submission_delete_by_admin(db, client):
# Check admin can delete submission
user = AdminFactory()
submission = ApplicationSubmissionFactory()

client.force_login(user)
delete_url = reverse("apply:submissions:delete", kwargs={"pk": submission.pk})
res = client.get(delete_url)
assert res.status_code == 200
assert "<form" in res.content.decode()
assert f'action="{delete_url}"' in res.content.decode()

res = client.post(delete_url, data={"delete": "delete"})
assert res.status_code == 302
assert res.url == "/apply/submissions/all/"

# Check submission is deleted
res = client.get(delete_url)
assert res.status_code == 404


def test_submission_delete_by_applicant(db, client):
user = ApplicantFactory()
submission = ApplicationSubmissionFactory(user=user, status=DRAFT_STATE)

client.force_login(user)
delete_url = reverse("apply:submissions:delete", kwargs={"pk": submission.pk})
res = client.get(delete_url)
assert res.status_code == 200
assert "<form" in res.content.decode()
assert f'action="{delete_url}"' in res.content.decode()

res = client.post(delete_url, data={"delete": "delete"})
assert res.status_code == 302
assert res.url == "/dashboard/"

# Check submission is deleted
res = client.get(delete_url)
assert res.status_code == 404

# Check user can't delete submission in other states
submission = ApplicationSubmissionFactory(user=user, status="internal_review")
delete_url = reverse("apply:submissions:delete", kwargs={"pk": submission.pk})
res = client.get(delete_url)
assert res.status_code == 403
25 changes: 17 additions & 8 deletions hypha/apply/funds/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
login_required,
user_passes_test,
)
from django.contrib.auth.mixins import UserPassesTestMixin
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.db.models import Count, Q
from django.forms import BaseModelForm
Expand Down Expand Up @@ -48,6 +48,7 @@
)
from django_tables2.paginators import LazyPaginator
from django_tables2.views import SingleTableMixin
from rolepermissions.checkers import has_object_permission

from hypha.apply.activity.messaging import MESSAGES, messenger
from hypha.apply.activity.models import Event
Expand Down Expand Up @@ -1698,15 +1699,22 @@ def get_queryset(self):
return RoundsAndLabs.objects.with_progress()


class SubmissionDeleteView(DeleteView):
class SubmissionDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
"""
View for deleting submissions with confirmation modal.
After successful deletion:
- Redirects applicants to their dashboard
- Redirects staff to the submissions list
- Creates delete notification unless author deleting own draft
"""

model = ApplicationSubmission

def dispatch(self, request, *args, **kwargs):
submission = self.get_object()
permission, _ = has_permission(
"submission_delete", request.user, submission, raise_exception=True
def test_func(self):
return has_object_permission(
"delete_submission", self.request.user, obj=self.get_object()
)
return super().dispatch(request, *args, **kwargs)

def get_success_url(self):
if self.request.user.is_applicant:
Expand All @@ -1725,7 +1733,8 @@ def form_valid(self, form):
source=submission,
)

# Delete NEW_SUBMISSION event for this particular submission
# Delete NEW_SUBMISSION event for this particular submission, if any.
# Otherwise, the submission deletion will fail.
Event.objects.filter(
type=MESSAGES.NEW_SUBMISSION, object_id=submission.id
).delete()
Expand Down
1 change: 1 addition & 0 deletions hypha/settings/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@
)

# django-rolepermissions
# https://django-role-permissions.readthedocs.io/en/stable/settings.html
ROLEPERMISSIONS_MODULE = "hypha.apply.users.roles"

# Default Auto field configuration
Expand Down
2 changes: 2 additions & 0 deletions hypha/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@
ELEVATE_COOKIE_SALT = SECRET_KEY

ENFORCE_TWO_FACTOR = False

SECURE_SSL_REDIRECT = False

0 comments on commit 8239c21

Please sign in to comment.