diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index b81f9ab46c..ef321b16e2 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -342,8 +342,12 @@ def render_field(self, field, parent_style): # Get a clone of the field with text-only value representation. field = field.as_form_field() - if style.get('input_type') == 'datetime-local' and isinstance(field.value, str): - field.value = field.value.rstrip('Z') + if style.get('input_type') == 'datetime-local': + # The format of an input type="datetime-local" is "yyyy-MM-ddThh:mm" + # followed by optional ":ss" or ":ss.SSS", so keep only the first three + # digits of milliseconds to avoid browser console error. + datetime_value = field._field.parent.validated_data.get(field.field_name) + field.value = datetime_value.replace(tzinfo=None).isoformat(timespec="milliseconds").rstrip('Z') if 'template' in style: template_name = style['template'] diff --git a/tests/test_renderers.py b/tests/test_renderers.py index 1b396575d4..522924cf61 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -1,5 +1,6 @@ import re from collections.abc import MutableMapping +from datetime import datetime import pytest from django.core.cache import cache @@ -488,6 +489,68 @@ class TestSerializer(serializers.Serializer): assert rendered == '' +class TestDateTimeFieldHTMLFormRender(TestCase): + def test_datetime_field_rendering_milliseconds(self): + class TestSerializer(serializers.Serializer): + appointment = serializers.DateTimeField() + + appointment = datetime(2024, 12, 24, 0, 55, 30, 345678) + serializer = TestSerializer(data={"appointment": appointment}) + serializer.is_valid() + renderer = HTMLFormRenderer() + field = serializer['appointment'] + rendered = renderer.render_field(field, {}) + self.assertInHTML( + '', + rendered + ) + + def test_datetime_field_rendering_no_milliseconds(self): + class TestSerializer(serializers.Serializer): + appointment = serializers.DateTimeField() + + appointment = datetime(2024, 12, 24, 0, 55, 30, 0) + serializer = TestSerializer(data={"appointment": appointment}) + serializer.is_valid() + renderer = HTMLFormRenderer() + field = serializer['appointment'] + rendered = renderer.render_field(field, {}) + self.assertInHTML( + '', + rendered + ) + + def test_datetime_field_rendering_no_seconds_and_no_milliseconds(self): + class TestSerializer(serializers.Serializer): + appointment = serializers.DateTimeField() + + appointment = datetime(2024, 12, 24, 0, 55, 0, 0) + serializer = TestSerializer(data={"appointment": appointment}) + serializer.is_valid() + renderer = HTMLFormRenderer() + field = serializer['appointment'] + rendered = renderer.render_field(field, {}) + self.assertInHTML( + '', + rendered + ) + + def test_datetime_field_rendering_with_format(self): + class TestSerializer(serializers.Serializer): + appointment = serializers.DateTimeField(format='%a %d %b %Y, %I:%M%p') + + appointment = datetime(2024, 12, 24, 0, 55, 30, 345678) + serializer = TestSerializer(data={"appointment": appointment}) + serializer.is_valid() + renderer = HTMLFormRenderer() + field = serializer['appointment'] + rendered = renderer.render_field(field, {}) + self.assertInHTML( + '', + rendered + ) + + class TestHTMLFormRenderer(TestCase): def setUp(self): class TestSerializer(serializers.Serializer):