From 0dc25526d8daaaa59968d4b1b597968e197f040c Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Tue, 13 Jul 2021 22:15:13 -0400 Subject: [PATCH] Fixed #32924 -- Changed BaseForm.get_initial_for_field() to remove microseconds when needed. --- django/forms/boundfield.py | 9 +-------- django/forms/forms.py | 6 ++++++ tests/forms_tests/tests/test_forms.py | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/django/forms/boundfield.py b/django/forms/boundfield.py index 54f9e9a64f..2ff8b0ee26 100644 --- a/django/forms/boundfield.py +++ b/django/forms/boundfield.py @@ -1,4 +1,3 @@ -import datetime import re from django.core.exceptions import ValidationError @@ -228,13 +227,7 @@ class BoundField: @cached_property def initial(self): - data = self.form.get_initial_for_field(self.field, self.name) - # If this is an auto-generated default date, nix the microseconds for - # standardized handling. See #22502. - if (isinstance(data, (datetime.datetime, datetime.time)) and - not self.field.widget.supports_microseconds): - data = data.replace(microsecond=0) - return data + return self.form.get_initial_for_field(self.field, self.name) def build_widget_attrs(self, attrs, widget=None): widget = widget or self.field.widget diff --git a/django/forms/forms.py b/django/forms/forms.py index ac6ef667d9..2bf268ae76 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -3,6 +3,7 @@ Form classes """ import copy +import datetime from django.core.exceptions import NON_FIELD_ERRORS, ValidationError from django.forms.fields import Field, FileField @@ -475,6 +476,11 @@ class BaseForm: value = self.initial.get(field_name, field.initial) if callable(value): value = value() + # If this is an auto-generated default date, nix the microseconds + # for standardized handling. See #22502. + if (isinstance(value, (datetime.datetime, datetime.time)) and + not field.widget.supports_microseconds): + value = value.replace(microsecond=0) return value diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index b4fb5737af..f3ee64ceda 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -1983,11 +1983,15 @@ Password: ) def test_get_initial_for_field(self): + now = datetime.datetime(2006, 10, 25, 14, 30, 45, 123456) + class PersonForm(Form): first_name = CharField(initial='John') last_name = CharField(initial='Doe') age = IntegerField() occupation = CharField(initial=lambda: 'Unknown') + dt_fixed = DateTimeField(initial=now) + dt_callable = DateTimeField(initial=lambda: now) form = PersonForm(initial={'first_name': 'Jane'}) cases = [ @@ -1997,6 +2001,9 @@ Password: ('first_name', 'Jane'), # Callables are evaluated. ('occupation', 'Unknown'), + # Microseconds are removed from datetimes. + ('dt_fixed', datetime.datetime(2006, 10, 25, 14, 30, 45)), + ('dt_callable', datetime.datetime(2006, 10, 25, 14, 30, 45)), ] for field_name, expected in cases: with self.subTest(field_name=field_name): @@ -2104,6 +2111,8 @@ Password: supports_microseconds = False class DateTimeForm(Form): + # Test a non-callable. + fixed = DateTimeField(initial=now) auto_timestamp = DateTimeField(initial=delayed_now) auto_time_only = TimeField(initial=delayed_now_time) supports_microseconds = DateTimeField(initial=delayed_now, widget=TextInput) @@ -2113,6 +2122,7 @@ Password: unbound = DateTimeForm() cases = [ + ('fixed', now_no_ms), ('auto_timestamp', now_no_ms), ('auto_time_only', now_no_ms.time()), ('supports_microseconds', now), @@ -2124,6 +2134,10 @@ Password: with self.subTest(field_name=field_name): actual = unbound[field_name].value() self.assertEqual(actual, expected) + # Also check get_initial_for_field(). + field = unbound.fields[field_name] + actual = unbound.get_initial_for_field(field, field_name) + self.assertEqual(actual, expected) def get_datetime_form_with_callable_initial(self, disabled, microseconds=0): class FakeTime: