Use attname to get pk value (#650)

* Use `attname` to get pk value

* Add tests for model as primary key

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add clarifying comments to _get_pk_value

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
evanandrews-xrd 2024-06-11 21:00:28 +10:00 committed by GitHub
parent 92805e2084
commit 2c0bd0fac6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 63 additions and 5 deletions

View file

@ -213,12 +213,13 @@ class LogEntryManager(models.Manager):
:type instance: Model
:return: The primary key value of the given model instance.
"""
pk_field = instance._meta.pk.name
# Should be equivalent to `instance.pk`.
pk_field = instance._meta.pk.attname
pk = getattr(instance, pk_field, None)
# Check to make sure that we got a pk not a model object.
if isinstance(pk, models.Model):
pk = self._get_pk_value(pk)
# Should be guaranteed as we used `attname` above, not `name`.
assert not isinstance(pk, models.Model)
return pk
def _get_serialized_data_or_none(self, instance):

View file

@ -57,6 +57,26 @@ class UUIDPrimaryKeyModel(models.Model):
history = AuditlogHistoryField(delete_related=True, pk_indexable=False)
class ModelPrimaryKeyModel(models.Model):
"""
A model with another model as primary key.
"""
key = models.OneToOneField(
"SimpleModel",
primary_key=True,
on_delete=models.CASCADE,
related_name="reverse_primary_key",
)
text = models.TextField(blank=True)
boolean = models.BooleanField(default=False)
integer = models.IntegerField(blank=True, null=True)
datetime = models.DateTimeField(auto_now=True)
history = AuditlogHistoryField(delete_related=True, pk_indexable=False)
class ProxyModel(SimpleModel):
"""
A model that is a proxy for another model.
@ -338,6 +358,7 @@ class AutoManyRelatedModel(models.Model):
auditlog.register(AltPrimaryKeyModel)
auditlog.register(UUIDPrimaryKeyModel)
auditlog.register(ModelPrimaryKeyModel)
auditlog.register(ProxyModel)
auditlog.register(RelatedModel)
auditlog.register(ManyRelatedModel)

View file

@ -20,7 +20,7 @@ from django.db import models
from django.db.models import JSONField, Value
from django.db.models.functions import Now
from django.db.models.signals import pre_save
from django.test import RequestFactory, TestCase, override_settings
from django.test import RequestFactory, TestCase, TransactionTestCase, override_settings
from django.urls import resolve, reverse
from django.utils import dateformat, formats
from django.utils import timezone as django_timezone
@ -46,6 +46,7 @@ from auditlog_tests.models import (
JSONModel,
ManyRelatedModel,
ManyRelatedOtherModel,
ModelPrimaryKeyModel,
NoDeleteHistoryModel,
NullableJSONModel,
PostgresArrayFieldModel,
@ -332,6 +333,41 @@ class UUIDPrimaryKeyModelModelWithActorTest(
pass
class ModelPrimaryKeyModelBase(SimpleModelTest):
def make_object(self):
self.key = super().make_object()
return ModelPrimaryKeyModel.objects.create(key=self.key, text="I am strange.")
class ModelPrimaryKeyModelTest(NoActorMixin, ModelPrimaryKeyModelBase):
pass
class ModelPrimaryKeyModelWithActorTest(WithActorMixin, ModelPrimaryKeyModelBase):
pass
# Must inherit from TransactionTestCase to use self.assertNumQueries.
class ModelPrimaryKeyTest(TransactionTestCase):
def test_get_pk_value(self):
"""
Test that the primary key can be retrieved without additional database queries.
"""
key = SimpleModel.objects.create(text="I am not difficult.")
obj = ModelPrimaryKeyModel.objects.create(key=key, text="I am strange.")
# Refresh the object so the primary key object is not cached.
obj.refresh_from_db()
with self.assertNumQueries(0):
pk = LogEntry.objects._get_pk_value(obj)
self.assertEqual(pk, obj.pk)
self.assertEqual(pk, key.pk)
# Sanity check: verify accessing obj.key causes database access.
with self.assertNumQueries(1):
pk = obj.key.pk
self.assertEqual(pk, obj.pk)
self.assertEqual(pk, key.pk)
class ProxyModelBase(SimpleModelTest):
def make_object(self):
return ProxyModel.objects.create(text="I am not what you think.")
@ -1206,7 +1242,7 @@ class RegisterModelSettingsTest(TestCase):
self.assertTrue(self.test_auditlog.contains(SimpleExcludeModel))
self.assertTrue(self.test_auditlog.contains(ChoicesFieldModel))
self.assertEqual(len(self.test_auditlog.get_models()), 26)
self.assertEqual(len(self.test_auditlog.get_models()), 27)
def test_register_models_register_model_with_attrs(self):
self.test_auditlog._register_models(