Fix get_field_value field default value handling for Django 6.0 compatibility (#726)

* 'get_field_value' - improve default value handling

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

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

* Add Test cases

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Youngkwang Yang 2025-06-09 22:00:28 +09:00 committed by GitHub
parent b640df67a3
commit 3a58e0a999
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 54 additions and 14 deletions

View file

@ -62,11 +62,31 @@ def get_field_value(obj, field, use_json_for_changes=False):
:return: The value of the field as a string.
:rtype: str
"""
def get_default_value():
"""
Attempts to get the default value for a field from the model's field definition.
:return: The default value of the field or None
"""
try:
model_field = obj._meta.get_field(field.name)
default = model_field.default
if default is NOT_PROVIDED:
return None
if callable(default):
return default()
return default
except AttributeError:
return None
try:
if isinstance(field, DateTimeField):
# DateTimeFields are timezone-aware, so we need to convert the field
# to its naive form before we can accurately compare them for changes.
value = getattr(obj, field.name, None)
value = getattr(obj, field.name)
try:
value = field.to_python(value)
except TypeError:
@ -78,30 +98,22 @@ def get_field_value(obj, field, use_json_for_changes=False):
):
value = django_timezone.make_naive(value, timezone=timezone.utc)
elif isinstance(field, JSONField):
value = field.to_python(getattr(obj, field.name, None))
value = field.to_python(getattr(obj, field.name))
if not use_json_for_changes:
try:
value = json.dumps(value, sort_keys=True, cls=field.encoder)
except TypeError:
pass
elif (field.one_to_one or field.many_to_one) and hasattr(field, "rel_class"):
value = smart_str(
getattr(obj, field.get_attname(), None), strings_only=True
)
value = smart_str(getattr(obj, field.get_attname()), strings_only=True)
else:
value = getattr(obj, field.name, None)
value = getattr(obj, field.name)
if not use_json_for_changes:
value = smart_str(value)
if type(value).__name__ == "__proxy__":
value = str(value)
except ObjectDoesNotExist:
value = (
field.default
if getattr(field, "default", NOT_PROVIDED) is not NOT_PROVIDED
else None
)
except (ObjectDoesNotExist, AttributeError):
return get_default_value()
return value

View file

@ -20,6 +20,7 @@ class SimpleModel(models.Model):
boolean = models.BooleanField(default=False)
integer = models.IntegerField(blank=True, null=True)
datetime = models.DateTimeField(auto_now=True)
char = models.CharField(null=True, max_length=100, default=lambda: "default value")
history = AuditlogHistoryField(delete_related=True)

View file

@ -2111,6 +2111,33 @@ class ModelInstanceDiffTest(TestCase):
msg="ObjectDoesNotExist should be handled",
)
def test_field_with_no_default_provided(self):
"""Field with no default (NOT_PROVIDED) should return None."""
first = SimpleModel(integer=1)
second = SimpleModel()
delattr(second, "integer")
changes = model_instance_diff(first, second)
self.assertEqual(
changes,
{"integer": ("1", "None")},
msg="field with no default should return None",
)
def test_field_with_callable_default(self):
first = SimpleModel(char="value")
second = SimpleModel()
delattr(second, "char")
changes = model_instance_diff(first, second)
self.assertEqual(
changes,
{"char": ("value", "default value")},
msg="callable default should be handled",
)
def test_diff_models_with_json_fields(self):
first = JSONModel.objects.create(
json={