Merge another part of upstream 2.2.0

This commit is contained in:
Aleh Rymašeŭski 2023-07-13 15:05:56 +00:00
commit 614f4bc4da
6 changed files with 77 additions and 30 deletions

View file

@ -1,7 +1,7 @@
---
repos:
- repo: https://github.com/psf/black
rev: 22.6.0
rev: 22.10.0
hooks:
- id: black
language_version: python3.8
@ -18,12 +18,12 @@ repos:
hooks:
- id: isort
- repo: https://github.com/asottile/pyupgrade
rev: v2.37.3
rev: v3.2.0
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.8.0
rev: 1.11.0
hooks:
- id: django-upgrade
args: [--target-version, "3.2"]
args: [--target-version, "3.2"]

View file

@ -3,11 +3,14 @@
#### Improvements
- feat: Add `serialized_data` field on `LogEntry` model. ([#412](https://github.com/jazzband/django-auditlog/pull/412))
- feat: Display the field name as it would be displayed in Django Admin or use `mapping_field` if available [#428](https://github.com/jazzband/django-auditlog/pull/428)
#### Fixes
- fix: Display `created` timestamp in server timezone ([#404](https://github.com/jazzband/django-auditlog/pull/404))
- fix: Handle port in `remote_addr` ([#417](https://github.com/jazzband/django-auditlog/pull/417))
- fix: Handle the error with AttributeError: 'OneToOneRel' error occur during a `PolymorphicModel` has relation with other models ([#429](https://github.com/jazzband/django-auditlog/pull/429))
- fix: Support search by custom USERNAME_FIELD ([#432](https://github.com/jazzband/django-auditlog/pull/432))
## 2.1.1 (2022-07-27)

View file

@ -26,6 +26,7 @@ class TimeLimitedPaginator(Paginator):
return super().count
@admin.register(LogEntry)
class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
list_display = ["created", "resource_url", "action", "msg_short", "user_url"]
search_fields = [
@ -34,7 +35,7 @@ class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
"changes",
"actor__first_name",
"actor__last_name",
"actor__{}".format(get_user_model().USERNAME_FIELD),
f"actor__{get_user_model().USERNAME_FIELD}",
]
list_filter = [
"action",
@ -60,6 +61,3 @@ class LogEntryAdmin(admin.ModelAdmin, LogEntryAdminMixin):
def has_delete_permission(self, request, obj=None):
return False
admin.site.register(LogEntry, LogEntryAdmin)

View file

@ -70,7 +70,11 @@ def get_field_value(obj, field):
else:
value = smart_str(getattr(obj, field.name, None))
except ObjectDoesNotExist:
value = field.default if field.default is not NOT_PROVIDED else None
value = (
field.default
if getattr(field, "default", NOT_PROVIDED) is not NOT_PROVIDED
else None
)
return value

View file

@ -2,7 +2,10 @@ import json
from django import urls as urlresolvers
from django.conf import settings
from django.contrib import admin
from django.core.exceptions import FieldDoesNotExist
from django.db.models import DateTimeField
from django.forms.utils import pretty_name
from django.template.defaultfilters import pluralize
from django.urls.exceptions import NoReverseMatch
from django.utils import timezone
@ -11,16 +14,17 @@ from django.utils.safestring import mark_safe
from django.utils.timezone import localtime
from auditlog.models import LogEntry
from auditlog.registry import auditlog
MAX = 75
class LogEntryAdminMixin:
@admin.display(description="Created")
def created(self, obj):
return localtime(obj.timestamp).strftime("%Y-%m-%d %H:%M:%S")
created.short_description = "Created"
@admin.display(description="User")
def user_url(self, obj):
if obj.actor:
app_label, model = settings.AUTH_USER_MODEL.split(".")
@ -33,8 +37,7 @@ class LogEntryAdminMixin:
return "system"
user_url.short_description = "User"
@admin.display(description="Resource")
def resource_url(self, obj):
app_label, model = obj.content_type.app_label, obj.content_type.model
viewname = f"admin:{app_label}_{model}_change"
@ -48,8 +51,7 @@ class LogEntryAdminMixin:
'<a href="{}">{} - {}</a>', link, obj.content_type, obj.object_repr
)
resource_url.short_description = "Resource"
@admin.display(description="Changes")
def msg_short(self, obj):
if obj.action == LogEntry.Action.DELETE:
return "" # delete
@ -61,8 +63,7 @@ class LogEntryAdminMixin:
fields = fields[:i] + " .."
return "%d change%s: %s" % (len(changes), s, fields)
msg_short.short_description = "Changes"
@admin.display(description="Changes")
def msg(self, obj):
changes = json.loads(obj.changes)
@ -86,7 +87,9 @@ class LogEntryAdminMixin:
msg.append("<table>")
msg.append(self._format_header("#", "Field", "From", "To"))
for i, (field, change) in enumerate(sorted(atom_changes.items()), 1):
value = [i, field] + (["***", "***"] if field == "password" else change)
value = [i, self.field_verbose_name(obj, field)] + (
["***", "***"] if field == "password" else change
)
if field in datetime_fields:
spotted_datetime_field = True
msg.append(self._format_line(*value))
@ -106,7 +109,7 @@ class LogEntryAdminMixin:
format_html(
"<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
i,
field,
self.field_verbose_name(obj, field),
change["operation"],
change_html,
)
@ -121,8 +124,6 @@ class LogEntryAdminMixin:
return mark_safe("".join(msg))
msg.short_description = "Changes"
def _get_datetime_fields(self, obj):
# only works for existing models and existing fields
try:
@ -160,3 +161,19 @@ class LogEntryAdminMixin:
)
)
return '<span class="timezonewarning">{}</span>'.format(warning_message)
def field_verbose_name(self, obj, field_name: str):
model = obj.content_type.model_class()
try:
model_fields = auditlog.get_model_fields(model._meta.model)
mapping_field_name = model_fields["mapping_fields"].get(field_name)
if mapping_field_name:
return mapping_field_name
except KeyError:
# Model definition in auditlog was probably removed
pass
try:
field = model._meta.get_field(field_name)
return pretty_name(getattr(field, "verbose_name", field_name))
except FieldDoesNotExist:
return pretty_name(field_name)

View file

@ -1145,7 +1145,6 @@ class ChoicesFieldModelTest(TestCase):
)
def test_changes_display_dict_single_choice(self):
self.assertEqual(
self.obj.history.latest().changes_display_dict["status"][1],
"Red",
@ -1281,7 +1280,7 @@ class AdminPanelTest(TestCase):
res = self.client.get(f"/admin/auditlog/logentry/{log_pk}/", follow=True)
self.assertEqual(res.status_code, 200)
res = self.client.get(f"/admin/auditlog/logentry/{log_pk}/delete/")
self.assertEqual(res.status_code, 200)
self.assertEqual(res.status_code, 403)
res = self.client.get(f"/admin/auditlog/logentry/{log_pk}/history/")
self.assertEqual(res.status_code, 200)
@ -1323,8 +1322,8 @@ class DiffMsgTest(TestCase):
(
"<table>"
"<tr><th>#</th><th>Field</th><th>From</th><th>To</th></tr>"
"<tr><td>1</td><td>field one</td><td>value before deletion</td><td>None</td></tr>"
"<tr><td>2</td><td>field two</td><td>11</td><td>None</td></tr>"
"<tr><td>1</td><td>Field one</td><td>value before deletion</td><td>None</td></tr>"
"<tr><td>2</td><td>Field two</td><td>11</td><td>None</td></tr>"
"</table>"
),
)
@ -1346,8 +1345,8 @@ class DiffMsgTest(TestCase):
(
"<table>"
"<tr><th>#</th><th>Field</th><th>From</th><th>To</th></tr>"
"<tr><td>1</td><td>field one</td><td>None</td><td>a value</td></tr>"
"<tr><td>2</td><td>field two</td><td>None</td><td>11</td></tr>"
"<tr><td>1</td><td>Field one</td><td>None</td><td>a value</td></tr>"
"<tr><td>2</td><td>Field two</td><td>None</td><td>11</td></tr>"
"</table>"
),
)
@ -1369,9 +1368,9 @@ class DiffMsgTest(TestCase):
(
"<table>"
"<tr><th>#</th><th>Field</th><th>From</th><th>To</th></tr>"
"<tr><td>1</td><td>field one</td><td>old value of field one</td>"
"<tr><td>1</td><td>Field one</td><td>old value of field one</td>"
"<td>new value of field one</td></tr>"
"<tr><td>2</td><td>field two</td><td>11</td><td>42</td></tr>"
"<tr><td>2</td><td>Field two</td><td>11</td><td>42</td></tr>"
"</table>"
),
)
@ -1394,12 +1393,38 @@ class DiffMsgTest(TestCase):
(
"<table>"
"<tr><th>#</th><th>Relationship</th><th>Action</th><th>Objects</th></tr>"
"<tr><td>1</td><td>some_m2m_field</td><td>add</td><td>Example User (user 1)"
"<tr><td>1</td><td>Some m2m field</td><td>add</td><td>Example User (user 1)"
"<br>Illustration (user 42)</td></tr>"
"</table>"
),
)
def test_unregister_after_log(self):
log_entry = self._create_log_entry(
LogEntry.Action.CREATE,
{
"field two": [None, 11],
"field one": [None, "a value"],
},
)
# Unregister
auditlog.unregister(SimpleModel)
self.assertEqual(
self.admin.msg_short(log_entry), "2 changes: field two, field one"
)
self.assertEqual(
self.admin.msg(log_entry),
(
"<table>"
"<tr><th>#</th><th>Field</th><th>From</th><th>To</th></tr>"
"<tr><td>1</td><td>Field one</td><td>None</td><td>a value</td></tr>"
"<tr><td>2</td><td>Field two</td><td>None</td><td>11</td></tr>"
"</table>"
),
)
# Re-register
auditlog.register(SimpleModel)
class NoDeleteHistoryTest(TestCase):
def test_delete_related(self):