Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-mm committed Oct 31, 2023
2 parents e93868c + 40b3975 commit 2e4f64c
Show file tree
Hide file tree
Showing 16 changed files with 412 additions and 76 deletions.
4 changes: 1 addition & 3 deletions django/contrib/admin/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ def delete_selected(modeladmin, request, queryset):
raise PermissionDenied
n = len(queryset)
if n:
for obj in queryset:
obj_display = str(obj)
modeladmin.log_deletion(request, obj, obj_display)
modeladmin.log_deletions(request, queryset)
modeladmin.delete_queryset(request, queryset)
modeladmin.message_user(
request,
Expand Down
56 changes: 56 additions & 0 deletions django/contrib/admin/models.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import json
import warnings

from django.conf import settings
from django.contrib.admin.utils import quote
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.urls import NoReverseMatch, reverse
from django.utils import timezone
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.text import get_text_list
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
Expand Down Expand Up @@ -33,6 +35,11 @@ def log_action(
action_flag,
change_message="",
):
warnings.warn(
"LogEntryManager.log_action() is deprecated. Use log_actions() instead.",
RemovedInDjango60Warning,
stacklevel=2,
)
if isinstance(change_message, list):
change_message = json.dumps(change_message)
return self.model.objects.create(
Expand All @@ -44,6 +51,55 @@ def log_action(
change_message=change_message,
)

def log_actions(
self, user_id, queryset, action_flag, change_message="", *, single_object=False
):
# RemovedInDjango60Warning.
if type(self).log_action != LogEntryManager.log_action:
warnings.warn(
"The usage of log_action() is deprecated. Implement log_actions() "
"instead.",
RemovedInDjango60Warning,
stacklevel=2,
)
return [
self.log_action(
user_id=user_id,
content_type_id=ContentType.objects.get_for_model(
obj, for_concrete_model=False
).id,
object_id=obj.pk,
object_repr=str(obj),
action_flag=action_flag,
change_message=change_message,
)
for obj in queryset
]

if isinstance(change_message, list):
change_message = json.dumps(change_message)

log_entry_list = [
self.model(
user_id=user_id,
content_type_id=ContentType.objects.get_for_model(
obj, for_concrete_model=False
).id,
object_id=obj.pk,
object_repr=str(obj)[:200],
action_flag=action_flag,
change_message=change_message,
)
for obj in queryset
]

if single_object and log_entry_list:
instance = log_entry_list[0]
instance.save()
return instance

return self.model.objects.bulk_create(log_entry_list)


class LogEntry(models.Model):
action_time = models.DateTimeField(
Expand Down
48 changes: 39 additions & 9 deletions django/contrib/admin/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import enum
import json
import re
import warnings
from functools import partial, update_wrapper
from urllib.parse import parse_qsl
from urllib.parse import quote as urlquote
Expand Down Expand Up @@ -54,6 +55,7 @@
from django.template.response import SimpleTemplateResponse, TemplateResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.html import format_html
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
Expand Down Expand Up @@ -945,13 +947,12 @@ def log_addition(self, request, obj, message):
"""
from django.contrib.admin.models import ADDITION, LogEntry

return LogEntry.objects.log_action(
return LogEntry.objects.log_actions(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
queryset=[obj],
action_flag=ADDITION,
change_message=message,
single_object=True,
)

def log_change(self, request, obj, message):
Expand All @@ -962,13 +963,12 @@ def log_change(self, request, obj, message):
"""
from django.contrib.admin.models import CHANGE, LogEntry

return LogEntry.objects.log_action(
return LogEntry.objects.log_actions(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
queryset=[obj],
action_flag=CHANGE,
change_message=message,
single_object=True,
)

def log_deletion(self, request, obj, object_repr):
Expand All @@ -978,6 +978,11 @@ def log_deletion(self, request, obj, object_repr):
The default implementation creates an admin LogEntry object.
"""
warnings.warn(
"ModelAdmin.log_deletion() is deprecated. Use log_deletions() instead.",
RemovedInDjango60Warning,
stacklevel=2,
)
from django.contrib.admin.models import DELETION, LogEntry

return LogEntry.objects.log_action(
Expand All @@ -988,6 +993,31 @@ def log_deletion(self, request, obj, object_repr):
action_flag=DELETION,
)

def log_deletions(self, request, queryset):
"""
Log that objects will be deleted. Note that this method must be called
before the deletion.
The default implementation creates admin LogEntry objects.
"""
from django.contrib.admin.models import DELETION, LogEntry

# RemovedInDjango60Warning.
if type(self).log_deletion != ModelAdmin.log_deletion:
warnings.warn(
"The usage of log_deletion() is deprecated. Implement log_deletions() "
"instead.",
RemovedInDjango60Warning,
stacklevel=2,
)
return [self.log_deletion(request, obj, str(obj)) for obj in queryset]

return LogEntry.objects.log_actions(
user_id=request.user.pk,
queryset=queryset,
action_flag=DELETION,
)

def action_checkbox(self, obj):
"""
A list_display column containing a checkbox widget.
Expand Down Expand Up @@ -2174,7 +2204,7 @@ def _delete_view(self, request, object_id, extra_context):
obj_display = str(obj)
attr = str(to_field) if to_field else self.opts.pk.attname
obj_id = obj.serializable_value(attr)
self.log_deletion(request, obj, obj_display)
self.log_deletions(request, [obj])
self.delete_model(request, obj)

return self.response_delete(request, obj_display, obj_id)
Expand Down
1 change: 1 addition & 0 deletions django/test/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,7 @@ def _follow_redirect(
extra["wsgi.url_scheme"] = url.scheme
if url.hostname:
extra["SERVER_NAME"] = url.hostname
extra["HTTP_HOST"] = url.hostname
if url.port:
extra["SERVER_PORT"] = str(url.port)

Expand Down
3 changes: 2 additions & 1 deletion docs/internals/deprecation.txt
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ details on these changes.
See the :ref:`Django 5.1 release notes <deprecated-features-5.1>` for more
details on these changes.

* ...
* The ``ModelAdmin.log_deletion()`` and ``LogEntryManager.log_action()``
methods will be removed.

.. _deprecation-removed-in-5.1:

Expand Down
5 changes: 4 additions & 1 deletion docs/releases/5.1.txt
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,10 @@ Features deprecated in 5.1
Miscellaneous
-------------

* ...
* The ``ModelAdmin.log_deletion()`` and ``LogEntryManager.log_action()``
methods are deprecated. Subclasses should implement
``ModelAdmin.log_deletions()`` and ``LogEntryManager.log_actions()``
instead.

Features removed in 5.1
=======================
Expand Down
6 changes: 2 additions & 4 deletions tests/admin_changelist/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
TO_FIELD_VAR,
)
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.messages.storage.cookie import CookieStorage
from django.db import DatabaseError, connection, models
from django.db.models import F, Field, IntegerField
Expand Down Expand Up @@ -1636,8 +1635,7 @@ def test_no_user(self):
"""{% get_admin_log %} works without specifying a user."""
user = User(username="jondoe", password="secret", email="super@example.com")
user.save()
ct = ContentType.objects.get_for_model(User)
LogEntry.objects.log_action(user.pk, ct.pk, user.pk, repr(user), 1)
LogEntry.objects.log_actions(user.pk, [user], 1, single_object=True)
context = Context({"log_entries": LogEntry.objects.all()})
t = Template(
"{% load log %}"
Expand All @@ -1646,7 +1644,7 @@ def test_no_user(self):
"{{ entry|safe }}"
"{% endfor %}"
)
self.assertEqual(t.render(context), "Added “<User: jondoe>”.")
self.assertEqual(t.render(context), "Added “jondoe”.")

def test_missing_args(self):
msg = "'get_admin_log' statements require two arguments"
Expand Down
24 changes: 24 additions & 0 deletions tests/admin_utils/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.contrib import admin
from django.contrib.admin.models import LogEntry, LogEntryManager
from django.db import models
from django.utils.translation import gettext_lazy as _

Expand Down Expand Up @@ -86,3 +87,26 @@ class Meta:

class Car(VehicleMixin):
pass


class InheritedLogEntryManager(LogEntryManager):
model = LogEntry

def log_action(
self,
user_id,
content_type_id,
object_id,
object_repr,
action_flag,
change_message="",
):
return LogEntry.objects.create(
user_id=user_id,
content_type_id=content_type_id,
object_id=str(object_id),
# Changing actual repr to test repr
object_repr="Test Repr",
action_flag=action_flag,
change_message=change_message,
)
Loading

0 comments on commit 2e4f64c

Please sign in to comment.