mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-04-23 16:24:47 +00:00
Merge another part of upstream 2.2.0
This commit is contained in:
commit
614f4bc4da
6 changed files with 77 additions and 30 deletions
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
Loading…
Reference in a new issue