parent
4f16376274
commit
f7394d2c32
|
@ -19,7 +19,7 @@ from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms.util import ErrorList, from_current_timezone, to_current_timezone
|
from django.forms.util import ErrorList, from_current_timezone, to_current_timezone
|
||||||
from django.forms.widgets import (
|
from django.forms.widgets import (
|
||||||
TextInput, PasswordInput, EmailInput, HiddenInput,
|
TextInput, PasswordInput, EmailInput, URLInput, HiddenInput,
|
||||||
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
|
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
|
||||||
NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
|
NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
|
||||||
SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION
|
SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION
|
||||||
|
@ -612,6 +612,7 @@ class ImageField(FileField):
|
||||||
return f
|
return f
|
||||||
|
|
||||||
class URLField(CharField):
|
class URLField(CharField):
|
||||||
|
widget = URLInput
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Enter a valid URL.'),
|
'invalid': _('Enter a valid URL.'),
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,8 @@ from django.utils.safestring import mark_safe
|
||||||
from django.utils import datetime_safe, formats, six
|
from django.utils import datetime_safe, formats, six
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'EmailInput', 'PasswordInput',
|
'Media', 'MediaDefiningClass', 'Widget', 'TextInput',
|
||||||
|
'EmailInput', 'URLInput', 'PasswordInput',
|
||||||
'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput',
|
'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput',
|
||||||
'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
|
'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
|
||||||
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
|
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
|
||||||
|
@ -255,6 +256,10 @@ class EmailInput(TextInput):
|
||||||
input_type = 'email'
|
input_type = 'email'
|
||||||
|
|
||||||
|
|
||||||
|
class URLInput(TextInput):
|
||||||
|
input_type = 'url'
|
||||||
|
|
||||||
|
|
||||||
class PasswordInput(TextInput):
|
class PasswordInput(TextInput):
|
||||||
input_type = 'password'
|
input_type = 'password'
|
||||||
|
|
||||||
|
|
|
@ -161,7 +161,7 @@ precedence::
|
||||||
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
|
>>> f = CommentForm(initial={'name': 'instance'}, auto_id=False)
|
||||||
>>> print(f)
|
>>> print(f)
|
||||||
<tr><th>Name:</th><td><input type="text" name="name" value="instance" /></td></tr>
|
<tr><th>Name:</th><td><input type="text" name="name" value="instance" /></td></tr>
|
||||||
<tr><th>Url:</th><td><input type="text" name="url" /></td></tr>
|
<tr><th>Url:</th><td><input type="url" name="url" /></td></tr>
|
||||||
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
|
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
|
||||||
|
|
||||||
Accessing "clean" data
|
Accessing "clean" data
|
||||||
|
|
|
@ -112,7 +112,7 @@ We've specified ``auto_id=False`` to simplify the output::
|
||||||
>>> f = CommentForm(auto_id=False)
|
>>> f = CommentForm(auto_id=False)
|
||||||
>>> print(f)
|
>>> print(f)
|
||||||
<tr><th>Your name:</th><td><input type="text" name="name" /></td></tr>
|
<tr><th>Your name:</th><td><input type="text" name="name" /></td></tr>
|
||||||
<tr><th>Your Web site:</th><td><input type="text" name="url" /></td></tr>
|
<tr><th>Your Web site:</th><td><input type="url" name="url" /></td></tr>
|
||||||
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
|
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
|
||||||
|
|
||||||
``initial``
|
``initial``
|
||||||
|
@ -135,7 +135,7 @@ field is initialized to a particular value. For example::
|
||||||
>>> f = CommentForm(auto_id=False)
|
>>> f = CommentForm(auto_id=False)
|
||||||
>>> print(f)
|
>>> print(f)
|
||||||
<tr><th>Name:</th><td><input type="text" name="name" value="Your name" /></td></tr>
|
<tr><th>Name:</th><td><input type="text" name="name" value="Your name" /></td></tr>
|
||||||
<tr><th>Url:</th><td><input type="text" name="url" value="http://" /></td></tr>
|
<tr><th>Url:</th><td><input type="url" name="url" value="http://" /></td></tr>
|
||||||
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
|
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
|
||||||
|
|
||||||
You may be thinking, why not just pass a dictionary of the initial values as
|
You may be thinking, why not just pass a dictionary of the initial values as
|
||||||
|
@ -150,7 +150,7 @@ and the HTML output will include any validation errors::
|
||||||
>>> f = CommentForm(default_data, auto_id=False)
|
>>> f = CommentForm(default_data, auto_id=False)
|
||||||
>>> print(f)
|
>>> print(f)
|
||||||
<tr><th>Name:</th><td><input type="text" name="name" value="Your name" /></td></tr>
|
<tr><th>Name:</th><td><input type="text" name="name" value="Your name" /></td></tr>
|
||||||
<tr><th>Url:</th><td><ul class="errorlist"><li>Enter a valid URL.</li></ul><input type="text" name="url" value="http://" /></td></tr>
|
<tr><th>Url:</th><td><ul class="errorlist"><li>Enter a valid URL.</li></ul><input type="url" name="url" value="http://" /></td></tr>
|
||||||
<tr><th>Comment:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="comment" /></td></tr>
|
<tr><th>Comment:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="comment" /></td></tr>
|
||||||
|
|
||||||
This is why ``initial`` values are only displayed for unbound forms. For bound
|
This is why ``initial`` values are only displayed for unbound forms. For bound
|
||||||
|
@ -805,7 +805,7 @@ For each field, we describe the default widget used if you don't specify
|
||||||
|
|
||||||
.. class:: URLField(**kwargs)
|
.. class:: URLField(**kwargs)
|
||||||
|
|
||||||
* Default widget: :class:`TextInput`
|
* Default widget: :class:`URLInput`
|
||||||
* Empty value: ``''`` (an empty string)
|
* Empty value: ``''`` (an empty string)
|
||||||
* Normalizes to: A Unicode object.
|
* Normalizes to: A Unicode object.
|
||||||
* Validates that the given value is a valid URL.
|
* Validates that the given value is a valid URL.
|
||||||
|
|
|
@ -139,7 +139,7 @@ provided for each widget will be rendered exactly the same::
|
||||||
>>> f = CommentForm(auto_id=False)
|
>>> f = CommentForm(auto_id=False)
|
||||||
>>> f.as_table()
|
>>> f.as_table()
|
||||||
<tr><th>Name:</th><td><input type="text" name="name" /></td></tr>
|
<tr><th>Name:</th><td><input type="text" name="name" /></td></tr>
|
||||||
<tr><th>Url:</th><td><input type="text" name="url"/></td></tr>
|
<tr><th>Url:</th><td><input type="url" name="url"/></td></tr>
|
||||||
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
|
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>
|
||||||
|
|
||||||
On a real Web page, you probably don't want every widget to look the same. You
|
On a real Web page, you probably don't want every widget to look the same. You
|
||||||
|
@ -160,7 +160,7 @@ Django will then include the extra attributes in the rendered output:
|
||||||
>>> f = CommentForm(auto_id=False)
|
>>> f = CommentForm(auto_id=False)
|
||||||
>>> f.as_table()
|
>>> f.as_table()
|
||||||
<tr><th>Name:</th><td><input type="text" name="name" class="special"/></td></tr>
|
<tr><th>Name:</th><td><input type="text" name="name" class="special"/></td></tr>
|
||||||
<tr><th>Url:</th><td><input type="text" name="url"/></td></tr>
|
<tr><th>Url:</th><td><input type="url" name="url"/></td></tr>
|
||||||
<tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>
|
<tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>
|
||||||
|
|
||||||
.. _styling-widget-classes:
|
.. _styling-widget-classes:
|
||||||
|
@ -403,6 +403,15 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
||||||
|
|
||||||
Text input: ``<input type="email" ...>``
|
Text input: ``<input type="email" ...>``
|
||||||
|
|
||||||
|
``URLInput``
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. class:: URLInput
|
||||||
|
|
||||||
|
.. versionadded:: 1.6
|
||||||
|
|
||||||
|
Text input: ``<input type="url" ...>``
|
||||||
|
|
||||||
``PasswordInput``
|
``PasswordInput``
|
||||||
~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,9 @@ Minor features
|
||||||
* Added :meth:`~django.db.models.query.QuerySet.earliest` for symmetry with
|
* Added :meth:`~django.db.models.query.QuerySet.earliest` for symmetry with
|
||||||
:meth:`~django.db.models.query.QuerySet.latest`.
|
:meth:`~django.db.models.query.QuerySet.latest`.
|
||||||
|
|
||||||
* The default widgets for :class:`~django.forms.EmailField` use
|
* The default widgets for :class:`~django.forms.EmailField` and
|
||||||
the new type attribute available in HTML5 (type='email').
|
:class:`~django.forms.URLField` use the new type attributes available in
|
||||||
|
HTML5 (type='email', type='url').
|
||||||
|
|
||||||
Backwards incompatible changes in 1.6
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
@ -44,7 +45,7 @@ Backwards incompatible changes in 1.6
|
||||||
|
|
||||||
* If your CSS/Javascript code used to access HTML input widgets by type, you
|
* If your CSS/Javascript code used to access HTML input widgets by type, you
|
||||||
should review it as ``type='text'`` widgets might be now output as
|
should review it as ``type='text'`` widgets might be now output as
|
||||||
``type='email'`` depending on their corresponding field type.
|
``type='email'`` or ``type='url'`` depending on their corresponding field type.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
|
|
@ -639,6 +639,7 @@ class FieldsTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_urlfield_1(self):
|
def test_urlfield_1(self):
|
||||||
f = URLField()
|
f = URLField()
|
||||||
|
self.assertWidgetRendersTo(f, '<input type="url" name="f" id="id_f" />')
|
||||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
|
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
|
||||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
|
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
|
||||||
self.assertEqual('http://localhost/', f.clean('http://localhost'))
|
self.assertEqual('http://localhost/', f.clean('http://localhost'))
|
||||||
|
@ -690,6 +691,7 @@ class FieldsTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_urlfield_5(self):
|
def test_urlfield_5(self):
|
||||||
f = URLField(min_length=15, max_length=20)
|
f = URLField(min_length=15, max_length=20)
|
||||||
|
self.assertWidgetRendersTo(f, '<input id="id_f" type="url" name="f" maxlength="20" />')
|
||||||
self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 15 characters (it has 13).'", f.clean, 'http://f.com')
|
self.assertRaisesMessage(ValidationError, "'Ensure this value has at least 15 characters (it has 13).'", f.clean, 'http://f.com')
|
||||||
self.assertEqual('http://example.com/', f.clean('http://example.com'))
|
self.assertEqual('http://example.com/', f.clean('http://example.com'))
|
||||||
self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 38).'", f.clean, 'http://abcdefghijklmnopqrstuvwxyz.com')
|
self.assertRaisesMessage(ValidationError, "'Ensure this value has at most 20 characters (it has 38).'", f.clean, 'http://abcdefghijklmnopqrstuvwxyz.com')
|
||||||
|
|
|
@ -102,22 +102,22 @@ class GenericAdminViewTest(TestCase):
|
||||||
# Works with no queryset
|
# Works with no queryset
|
||||||
formset = EpisodeMediaFormSet(instance=e)
|
formset = EpisodeMediaFormSet(instance=e)
|
||||||
self.assertEqual(len(formset.forms), 5)
|
self.assertEqual(len(formset.forms), 5)
|
||||||
self.assertHTMLEqual(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/podcast.mp3" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.mp3_media_pk)
|
self.assertHTMLEqual(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="url" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/podcast.mp3" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.mp3_media_pk)
|
||||||
self.assertHTMLEqual(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>' % self.png_media_pk)
|
self.assertHTMLEqual(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="url" name="generic_inline_admin-media-content_type-object_id-1-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>' % self.png_media_pk)
|
||||||
self.assertHTMLEqual(formset.forms[2].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-2-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-2-url" type="text" name="generic_inline_admin-media-content_type-object_id-2-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-2-id" id="id_generic_inline_admin-media-content_type-object_id-2-id" /></p>')
|
self.assertHTMLEqual(formset.forms[2].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-2-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-2-url" type="url" name="generic_inline_admin-media-content_type-object_id-2-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-2-id" id="id_generic_inline_admin-media-content_type-object_id-2-id" /></p>')
|
||||||
|
|
||||||
# A queryset can be used to alter display ordering
|
# A queryset can be used to alter display ordering
|
||||||
formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.order_by('url'))
|
formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.order_by('url'))
|
||||||
self.assertEqual(len(formset.forms), 5)
|
self.assertEqual(len(formset.forms), 5)
|
||||||
self.assertHTMLEqual(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.png_media_pk)
|
self.assertHTMLEqual(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="url" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.png_media_pk)
|
||||||
self.assertHTMLEqual(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" value="http://example.com/podcast.mp3" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>' % self.mp3_media_pk)
|
self.assertHTMLEqual(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="url" name="generic_inline_admin-media-content_type-object_id-1-url" value="http://example.com/podcast.mp3" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>' % self.mp3_media_pk)
|
||||||
self.assertHTMLEqual(formset.forms[2].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-2-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-2-url" type="text" name="generic_inline_admin-media-content_type-object_id-2-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-2-id" id="id_generic_inline_admin-media-content_type-object_id-2-id" /></p>')
|
self.assertHTMLEqual(formset.forms[2].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-2-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-2-url" type="url" name="generic_inline_admin-media-content_type-object_id-2-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-2-id" id="id_generic_inline_admin-media-content_type-object_id-2-id" /></p>')
|
||||||
|
|
||||||
# Works with a queryset that omits items
|
# Works with a queryset that omits items
|
||||||
formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.filter(url__endswith=".png"))
|
formset = EpisodeMediaFormSet(instance=e, queryset=Media.objects.filter(url__endswith=".png"))
|
||||||
self.assertEqual(len(formset.forms), 4)
|
self.assertEqual(len(formset.forms), 4)
|
||||||
self.assertHTMLEqual(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="text" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.png_media_pk)
|
self.assertHTMLEqual(formset.forms[0].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-0-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-0-url" type="url" name="generic_inline_admin-media-content_type-object_id-0-url" value="http://example.com/logo.png" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-0-id" value="%s" id="id_generic_inline_admin-media-content_type-object_id-0-id" /></p>' % self.png_media_pk)
|
||||||
self.assertHTMLEqual(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="text" name="generic_inline_admin-media-content_type-object_id-1-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>')
|
self.assertHTMLEqual(formset.forms[1].as_p(), '<p><label for="id_generic_inline_admin-media-content_type-object_id-1-url">Url:</label> <input id="id_generic_inline_admin-media-content_type-object_id-1-url" type="url" name="generic_inline_admin-media-content_type-object_id-1-url" maxlength="200" /><input type="hidden" name="generic_inline_admin-media-content_type-object_id-1-id" id="id_generic_inline_admin-media-content_type-object_id-1-id" /></p>')
|
||||||
|
|
||||||
def testGenericInlineFormsetFactory(self):
|
def testGenericInlineFormsetFactory(self):
|
||||||
# Regression test for #10522.
|
# Regression test for #10522.
|
||||||
|
|
Loading…
Reference in New Issue