diff --git a/django/forms/fields.py b/django/forms/fields.py index 1a58a60743..4e8e903e86 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -19,6 +19,7 @@ from django.conf import settings from django.core import validators from django.core.exceptions import ValidationError from django.forms.boundfield import BoundField +from django.forms.fields import Field as DjangoField from django.forms.utils import from_current_timezone, to_current_timezone from django.forms.widgets import ( FILE_INPUT_CONTRADICTION, @@ -29,6 +30,7 @@ from django.forms.widgets import ( EmailInput, FileInput, HiddenInput, + Input, MultipleHiddenInput, NullBooleanSelect, NumberInput, @@ -1408,3 +1410,14 @@ class JSONField(CharField): return json.dumps(initial, sort_keys=True, cls=self.encoder) != json.dumps( self.to_python(data), sort_keys=True, cls=self.encoder ) + + +class CustomField(DjangoField): + def __init__(self, *, widget=None, placeholder=None, **kwargs): + self.placeholder = placeholder + widget = widget or Input() + + if self.placeholder: + widget.attrs.setdefault("placeholder", self.placeholder) + + super().__init__(widget=widget, **kwargs) diff --git a/django/forms/templates/django/forms/widgets/input.html b/django/forms/templates/django/forms/widgets/input.html index 9010a92145..2a7cc5e504 100644 --- a/django/forms/templates/django/forms/widgets/input.html +++ b/django/forms/templates/django/forms/widgets/input.html @@ -1 +1,3 @@ + +{% if widget.attrs.placeholder %}placeholder="{{ widget.attrs.placeholder }}" {% endif %}> diff --git a/django/forms/widgets.py b/django/forms/widgets.py index ca5f2724db..3bb9b0bcac 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -1226,3 +1226,25 @@ class SelectDateWidget(Widget): ("{}_{}".format(name, interval) in data) for interval in ("year", "month", "day") ) + + +class Input(Widget): + input_type = None # Subclass must define this + + def __init__(self, attrs=None, placeholder=None): + self.placeholder = placeholder # Add placeholder attribute + super().__init__(attrs) + + def get_context(self, name, value, attrs): + context = super().get_context(name, value, attrs) + if self.placeholder: + attrs = attrs or {} + attrs.setdefault("placeholder", self.placeholder) + context["widget"]["attrs"] = attrs + return context + + def render(self, name, value, attrs=None, renderer=None): + attrs = attrs or {} + if self.placeholder: + attrs["placeholder"] = self.placeholder + return super().render(name, value, attrs, renderer) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 2b4b344844..47ce86c7ee 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -1627,3 +1627,13 @@ only requirements are that it implement a ``clean()`` method and that its You can also customize how a field will be accessed by overriding :meth:`~django.forms.Field.get_bound_field()`. + +DateField +--------- +Use this field to input dates. The default expected format is ``YYYY-MM-DD``. +For example: ``2024-12-31``. + +TimeField +--------- +Use this field to input time. The default expected format is ``HH:MM``. +For example: ``14:30``. \ No newline at end of file diff --git a/tests/forms_tests/tests/test_widgets.py b/tests/forms_tests/tests/test_widgets.py index 2718b1d2f1..20fc991698 100644 --- a/tests/forms_tests/tests/test_widgets.py +++ b/tests/forms_tests/tests/test_widgets.py @@ -1,5 +1,6 @@ from django.contrib.admin.tests import AdminSeleniumTestCase -from django.test import override_settings +from django.forms import CharField, Form, TextInput +from django.test import SimpleTestCase, override_settings from django.urls import reverse from ..models import Article @@ -22,3 +23,17 @@ class LiveWidgetTests(AdminSeleniumTestCase): self.selenium.find_element(By.ID, "submit").click() article = Article.objects.get(pk=article.pk) self.assertEqual(article.content, "\r\nTst\r\n") + + +class WidgetPlaceholderTests(SimpleTestCase): + def test_placeholder_in_input_widget(self): + widget = TextInput(attrs={"placeholder": "Enter text"}) + output = widget.render("name", "") + self.assertIn('placeholder="Enter text"', output) + + def test_placeholder_in_field(self): + class ExampleForm(Form): + name = CharField(widget=TextInput(attrs={"placeholder": "Your name"})) + + form = ExampleForm() + self.assertIn('placeholder="Your name"', str(form["name"]))