Repository: jazzband/django-auditlog (language: python)

Issue:
Can't remove any auditlogged objects through the admin
As @madomdy [commented](https://github.com/jazzband/django-auditlog/pull/449#issuecomment-1370397139) in a related pull request, they are unable to remove objects of any model connected to the audit log.

![screenshot](https://user-images.githubusercontent.com/5181983/210469079-1eda4086-90cb-4334-a937-112d513234c4.png)

Possibly minimal app to reproduce the issue (I haven't tested it):
```python
# model.py
from auditlog.models import AuditlogHistoryField
from django.db import models


class MyModel(models.Model):
    history = AuditlogHistoryField()    

# admin.py
from django.contrib import admin

from .models import MyModel


admin.site.register(MyModel)
```

Instances of `MyModel` can't be removed through the admin because of `AuditlogHistoryField` and the delete permissions retracted in #449.

Workaround: pass `delete_related=False` to `AuditlogHistoryField` (`history = AuditlogHistoryField(delete_related=False)`).


Discussion / hints:
I also couldn't reproduce the problem
Seems to be related to AuditlogHistoryField, I added `delete_related=False` to AuditlogHistoryField(delete_related=False) as a fix, haven't investigated further.
Thanks @ilhan, that was the missing piece of the puzzle. I'll update the description of the issue.
Wouldn't it be much more flexible, if we can not only set this to True or False but have the same values like in normal referential integrity (SET_NULL, CASCADE, PROTECT)?
@christianwgd, your comment made me investigate this flag more deeply. I had the impression that `delete_related` was a standard parameter of `GenericRelation` and I was about to say so. I was wrong.

At the first glance it makes sense to support `on_delete` keyword. But on the other hand, how valuable `SET_NULL` (and the rest of the family of `SET` options) may be for the audit log table?

@hramezani, if we change `delete_related` to be `False` by default (or make an equivalent change through the potential new `on_delete` parameter), this will be a breaking change. We should include it in the 3.0 release. Do you think we should revert #449 until 3.0 too?
@alieh-rymasheuski, I thought about the SET_NULL option and found it useful, if you want to see, that someone has deleted an object and maybe recall the history of that. Yes, maybe it's not the most used one, but as I understood the "delete_related" option, it would exactly be like SET_NULL if set to False (delete object and keep the audit log). And the "PROTECT" value would exactly do, what "delete_related" would to, if set to True. What I am missing is CASCADE, delete the object and delete all connected audit log items.
But anyway thanks for investigating...
Effect of builtin `on_delete` options as compared to `delete_related`.

| on_delete   | delete_related | comment                                                                                                                           |
|-------------|----------------|-----------------------------------------------------------------------------------------------------------------------------------|
| CASCADE     | True           | This is the default behavior of `GenericRelation`. It was broken in #449 and now the default behavior is similar to `PROTECT`     |
| DO_NOTHING  | False          | As for me, this is the only correct behavior ;)                                                                                   |
| PROTECT     |                | May make sense if we don't track `create` actions, otherwise this option will disable deletion altogether                            |
| RESTRICT    |                | Can't think of a scenario when it will be different from `PROTECT`                                                                |
| SET         |                | May be used to mark auditlog of deleted objects, but I'd not recommend mangling auditlog like that                                |
| SET_NULL    |                | Same reasoning as for `SET`. Additionally, this will fail if used together with `pk_indexable=False` as `object_pk` is not nullable |
| SET_DEFAULT |                | Given that `object_id` and `object_pk` have no default, this will have no effect                                                                          |
Your'e right. I would reduce my requirement to have a third value beyond True and False. As I understood the fix in #449 "changes" the delete_related value from "PROTECT" to "CASCADE" and for me the new behaviour of the True value is all I need, but I could imagine cases where I would like to set it to the old behaviour.
@alieh-rymasheuski I think we need to revert https://github.com/jazzband/django-auditlog/pull/449. 
I've created [V2 branch](https://github.com/jazzband/django-auditlog/tree/v2) on top of `2.2.1`.
Could you please create a PR base on `V2 branch` and revert it? Then I will release `2.2.2`
 
[django-auditlog 2.2.2](https://pypi.org/project/django-auditlog/) has been released!

Hey @hramezani , can you please update the [CHANGELOG.md](https://github.com/jazzband/django-auditlog/blob/master/CHANGELOG.md) file, so the 2.2.2 release notes are present in their own section.
Hey @suricactus, I've created a PR to add it https://github.com/jazzband/django-auditlog/pull/510

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 73a35692..611d4b1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,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):


Test patch:
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):