From 1d61c9aa4c10d643de5e0fae9445eb5758e70e0b Mon Sep 17 00:00:00 2001 From: Tosinibikunle Date: Thu, 7 Aug 2025 21:54:51 +0100 Subject: [PATCH 1/4] fixed postgre type mismatch error in mixins.py --- auditlog/mixins.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/auditlog/mixins.py b/auditlog/mixins.py index b0cdc45..07ea950 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 @@ -137,9 +138,9 @@ 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") ) From 4fb1c11f68cba7df5e32366df2bd3623039dd9c9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 7 Aug 2025 20:57:09 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- auditlog/mixins.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/auditlog/mixins.py b/auditlog/mixins.py index 07ea950..c656dba 100644 --- a/auditlog/mixins.py +++ b/auditlog/mixins.py @@ -138,9 +138,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__) + ct = ContentType.objects.get_for_model(obj.__class__) log_entries = ( - LogEntry.objects.filter(content_type=ct, object_pk=str(obj.pk)) # <--- Fixed here + LogEntry.objects.filter( + content_type=ct, object_pk=str(obj.pk) + ) # <--- Fixed here .select_related("actor") .order_by("-timestamp") ) From a8cb1c946453fc10e363a8d832e6f4c275722128 Mon Sep 17 00:00:00 2001 From: Tosinibikunle Date: Thu, 14 Aug 2025 17:36:27 +0100 Subject: [PATCH 3/4] fix: Fix type mismatch in LogEntry object_pk filter and update tests --- CHANGELOG.md | 7 +++++ auditlog/models.py | 57 +++++++++++++++-------------------------- auditlog_tests/tests.py | 2 +- 3 files changed, 29 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ba50d3..521ed9d 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 audit log history view to Django Admin. ([#743](https://github.com/jazzband/django-auditlog/pull/743)) diff --git a/auditlog/models.py b/auditlog/models.py index 01b54a0..1e18270 100644 --- a/auditlog/models.py +++ b/auditlog/models.py @@ -146,47 +146,32 @@ 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): - """ - Get log entries for the objects in the specified queryset. + """ + Get log entries for the objects in the specified queryset. - :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 - """ - if not isinstance(queryset, QuerySet) or queryset.count() == 0: - return self.none() + :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 """ + if not isinstance(queryset, QuerySet) or queryset.count() == 0: + return self.none() - content_type = ContentType.objects.get_for_model(queryset.model) - primary_keys = list( - queryset.values_list(queryset.model._meta.pk.name, flat=True) - ) + content_type = ContentType.objects.get_for_model(queryset.model) + primary_keys = list( + 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 f3056bc..dcf62f6 100644 --- a/auditlog_tests/tests.py +++ b/auditlog_tests/tests.py @@ -183,7 +183,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() From d630437533eb05e37a664541bbdd9404315c4210 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 16:37:31 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- auditlog/models.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/auditlog/models.py b/auditlog/models.py index 1e18270..27b3a32 100644 --- a/auditlog/models.py +++ b/auditlog/models.py @@ -149,29 +149,29 @@ class LogEntryManager(models.Manager): return self.filter(content_type=content_type, object_pk=smart_str(pk)) def get_for_objects(self, queryset): - """ - Get log entries for the objects in the specified queryset. + """ + Get log entries for the objects in the specified queryset. - :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 """ - if not isinstance(queryset, QuerySet) or queryset.count() == 0: - return self.none() + :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""" + if not isinstance(queryset, QuerySet) or queryset.count() == 0: + return self.none() - content_type = ContentType.objects.get_for_model(queryset.model) - primary_keys = list( - queryset.values_list(queryset.model._meta.pk.name, flat=True) - ) + content_type = ContentType.objects.get_for_model(queryset.model) + primary_keys = list( + queryset.values_list(queryset.model._meta.pk.name, flat=True) + ) - # Always compare as strings for PostgreSQL compatibility - primary_keys = [smart_str(pk) for pk in primary_keys] + # 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() - ) + return ( + self.filter(content_type=content_type) + .filter(Q(object_pk__in=primary_keys)) + .distinct() + ) def get_for_model(self, model): """