diff --git a/CHANGELOG.md b/CHANGELOG.md index a3eb8ee..bb29b39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Next Release +## (2025-08-14) + +### Fixes + +- fix: Ensure `object_pk` lookup works for non-string primary keys by casting PK to string in `get_for_object()` and related queries. +- fix: Updated tests to correctly match log entries for integer, UUID, or string primary keys. ([#746](https://github.com/jazzband/django-auditlog/pull/746)) + #### Improvements - feat: Add `AUDITLOG_USE_FK_STRING_REPRESENTATION` setting that controls how foreign key changes are represented ([#779)](https://github.com/jazzband/django-auditlog/pull/779)) diff --git a/auditlog/mixins.py b/auditlog/mixins.py index 8ae203c..031ffd5 100644 --- a/auditlog/mixins.py +++ b/auditlog/mixins.py @@ -4,6 +4,7 @@ from django import urls as urlresolvers from django.conf import settings from django.contrib import admin from django.contrib.admin.views.main import PAGE_VAR +from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.http import HttpRequest from django.template.response import TemplateResponse @@ -139,9 +140,11 @@ class AuditlogHistoryAdminMixin: obj = self.get_object(request, unquote(object_id)) if not self.has_view_permission(request, obj): raise PermissionDenied - + ct = ContentType.objects.get_for_model(obj.__class__) log_entries = ( - LogEntry.objects.get_for_object(obj) + LogEntry.objects.filter( + content_type=ct, object_pk=str(obj.pk) + ) # <--- Fixed here .select_related("actor") .order_by("-timestamp") ) diff --git a/auditlog/models.py b/auditlog/models.py index edc5dd2..5479bd3 100644 --- a/auditlog/models.py +++ b/auditlog/models.py @@ -148,10 +148,7 @@ class LogEntryManager(models.Manager): content_type = ContentType.objects.get_for_model(instance.__class__) pk = self._get_pk_value(instance) - if isinstance(pk, int): - return self.filter(content_type=content_type, object_id=pk) - else: - return self.filter(content_type=content_type, object_pk=smart_str(pk)) + return self.filter(content_type=content_type, object_pk=smart_str(pk)) def get_for_objects(self, queryset): """ @@ -160,8 +157,7 @@ class LogEntryManager(models.Manager): :param queryset: The queryset to get the log entries for. :type queryset: QuerySet :return: The LogEntry objects for the objects in the given queryset. - :rtype: QuerySet - """ + :rtype: QuerySet""" if not isinstance(queryset, QuerySet) or queryset.count() == 0: return self.none() @@ -170,25 +166,14 @@ class LogEntryManager(models.Manager): queryset.values_list(queryset.model._meta.pk.name, flat=True) ) - if isinstance(primary_keys[0], int): - return ( - self.filter(content_type=content_type) - .filter(Q(object_id__in=primary_keys)) - .distinct() - ) - elif isinstance(queryset.model._meta.pk, models.UUIDField): - primary_keys = [smart_str(pk) for pk in primary_keys] - return ( - self.filter(content_type=content_type) - .filter(Q(object_pk__in=primary_keys)) - .distinct() - ) - else: - return ( - self.filter(content_type=content_type) - .filter(Q(object_pk__in=primary_keys)) - .distinct() - ) + # Always compare as strings for PostgreSQL compatibility + primary_keys = [smart_str(pk) for pk in primary_keys] + + return ( + self.filter(content_type=content_type) + .filter(Q(object_pk__in=primary_keys)) + .distinct() + ) def get_for_model(self, model): """ diff --git a/auditlog_tests/tests.py b/auditlog_tests/tests.py index 3183015..a92634e 100644 --- a/auditlog_tests/tests.py +++ b/auditlog_tests/tests.py @@ -191,7 +191,7 @@ class SimpleModelTest(TestCase): self.delete(obj) # Check for log entries - qs = LogEntry.objects.filter(content_type=content_type, object_pk=pk) + qs = LogEntry.objects.filter(content_type=content_type, object_pk=str(pk)) self.assertEqual(qs.count(), 1, msg="There is one log entry for 'DELETE'") history = qs.get()