diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6028b6..80921989 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ - fix: Always set remote_addr even if the request has no authenticated user. ([#484](https://github.com/jazzband/django-auditlog/pull/484)) - fix: Fix a bug in getting field's `verbose_name` when model is not accessible. ([508](https://github.com/jazzband/django-auditlog/pull/508)) - fix: Fix a bug in `serialized_data` with F expressions. ([508](https://github.com/jazzband/django-auditlog/pull/508)) +- fix: Make log entries read-only in the admin. ([#449](https://github.com/jazzband/django-auditlog/pull/449), [#556](https://github.com/jazzband/django-auditlog/pull/556)) (applied again after being reverted in 2.2.2) ## 2.2.2 (2023-01-16) diff --git a/auditlog/admin.py b/auditlog/admin.py index 454a0f1e..130f4806 100644 --- a/auditlog/admin.py +++ b/auditlog/admin.py @@ -1,3 +1,5 @@ +from functools import cached_property + from django.contrib import admin from django.contrib.auth import get_user_model from django.utils.translation import gettext_lazy as _ @@ -39,7 +41,17 @@ def has_add_permission(self, request): def has_change_permission(self, request, obj=None): return False + @cached_property + def _own_url_names(self): + return [pattern.name for pattern in self.urls if pattern.name] + def has_delete_permission(self, request, obj=None): + if ( + request.resolver_match + and request.resolver_match.url_name not in self._own_url_names + ): + # only allow cascade delete to satisfy delete_related flag + return super().has_delete_permission(request, obj) return False def get_queryset(self, request): diff --git a/auditlog_tests/tests.py b/auditlog_tests/tests.py index 12a47250..8a84385c 100644 --- a/auditlog_tests/tests.py +++ b/auditlog_tests/tests.py @@ -19,7 +19,7 @@ from django.db import models from django.db.models.signals import pre_save from django.test import RequestFactory, TestCase, override_settings -from django.urls import reverse +from django.urls import resolve, reverse from django.utils import dateformat, formats from django.utils import timezone as django_timezone from django.utils.encoding import smart_str @@ -1549,6 +1549,23 @@ def test_cid(self): self.assertEqual(res.status_code, 200) self.assertIn(expected_response, res.rendered_content) + def test_has_delete_permission(self): + log = self.obj.history.latest() + obj_pk = self.obj.pk + delete_log_request = RequestFactory().post( + f"/admin/auditlog/logentry/{log.pk}/delete/" + ) + delete_log_request.resolver_match = resolve(delete_log_request.path) + delete_log_request.user = self.user + delete_object_request = RequestFactory().post( + f"/admin/tests/simplemodel/{obj_pk}/delete/" + ) + delete_object_request.resolver_match = resolve(delete_object_request.path) + delete_object_request.user = self.user + + self.assertTrue(self.admin.has_delete_permission(delete_object_request, log)) + self.assertFalse(self.admin.has_delete_permission(delete_log_request, log)) + class DiffMsgTest(TestCase): def setUp(self):