Skip to content
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
11 changes: 10 additions & 1 deletion openwisp_utils/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,16 @@ def has_add_permission(self, request):
return False

def has_delete_permission(self, request, obj=None):
return False
opts = self.model._meta
resolver_match = getattr(request, "resolver_match", None)
url_name = getattr(resolver_match, "url_name", None)
if url_name and url_name in (
f"{opts.app_label}_{opts.model_name}_delete",
f"{opts.app_label}_{opts.model_name}_change",
f"{opts.app_label}_{opts.model_name}_changelist",
):
return False
return super().has_delete_permission(request, obj)

def save_model(self, request, obj, form, change): # pragma: nocover
pass
Expand Down
34 changes: 34 additions & 0 deletions tests/test_project/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,40 @@ class TestReadOnlyAdmin(ReadOnlyAdmin):
["id", "session_id", "username", "start_time", "stop_time"],
)

def test_readonlyadmin_has_delete_permission(self):
modeladmin = ReadOnlyAdmin(RadiusAccounting, AdminSite())

with self.subTest("changelist URL returns False"):
request = self.client.get(
reverse("admin:test_project_radiusaccounting_changelist")
).wsgi_request
self.assertFalse(modeladmin.has_delete_permission(request))

with self.subTest("change URL returns False"):
obj = self._create_radius_accounting(username="test", session_id="1")
request = self.client.get(
reverse("admin:test_project_radiusaccounting_change", args=[obj.pk])
).wsgi_request
self.assertFalse(modeladmin.has_delete_permission(request))

with self.subTest("cascade delete from unrelated URL returns True"):
# Simulate being called from a parent model's delete
# confirmation (cascade), not from the model's own views.
request = self.client.get(
reverse("admin:test_project_radiusaccounting_changelist")
).wsgi_request
mock_resolver = MagicMock()
mock_resolver.url_name = "index"
request.resolver_match = mock_resolver
self.assertTrue(modeladmin.has_delete_permission(request))

with self.subTest("no resolver_match returns True"):
request = self.client.get(
reverse("admin:test_project_radiusaccounting_changelist")
).wsgi_request
request.resolver_match = None
self.assertTrue(modeladmin.has_delete_permission(request))

def test_context_processor(self):
url = reverse("admin:index")
response = self.client.get(url)
Expand Down
Loading