mirror of https://github.com/django/django.git
Fixed #19686 -- Added HTML5 number input type
Thanks Simon Charette for his help on the patch. Refs #16630.
This commit is contained in:
parent
dcf651c27e
commit
7ec2a21be1
|
@ -19,7 +19,7 @@ from django.core import validators
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.forms.util import ErrorList, from_current_timezone, to_current_timezone
|
||||
from django.forms.widgets import (
|
||||
TextInput, PasswordInput, EmailInput, URLInput, HiddenInput,
|
||||
TextInput, NumberInput, EmailInput, URLInput, HiddenInput,
|
||||
MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
|
||||
NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
|
||||
SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION
|
||||
|
@ -234,6 +234,7 @@ class IntegerField(Field):
|
|||
|
||||
def __init__(self, max_value=None, min_value=None, *args, **kwargs):
|
||||
self.max_value, self.min_value = max_value, min_value
|
||||
kwargs.setdefault('widget', NumberInput if not kwargs.get('localize') else self.widget)
|
||||
super(IntegerField, self).__init__(*args, **kwargs)
|
||||
|
||||
if max_value is not None:
|
||||
|
@ -257,6 +258,16 @@ class IntegerField(Field):
|
|||
raise ValidationError(self.error_messages['invalid'])
|
||||
return value
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
attrs = super(IntegerField, self).widget_attrs(widget)
|
||||
if isinstance(widget, NumberInput):
|
||||
if self.min_value is not None:
|
||||
attrs['min'] = self.min_value
|
||||
if self.max_value is not None:
|
||||
attrs['max'] = self.max_value
|
||||
return attrs
|
||||
|
||||
|
||||
class FloatField(IntegerField):
|
||||
default_error_messages = {
|
||||
'invalid': _('Enter a number.'),
|
||||
|
@ -278,25 +289,24 @@ class FloatField(IntegerField):
|
|||
raise ValidationError(self.error_messages['invalid'])
|
||||
return value
|
||||
|
||||
class DecimalField(Field):
|
||||
def widget_attrs(self, widget):
|
||||
attrs = super(FloatField, self).widget_attrs(widget)
|
||||
if isinstance(widget, NumberInput):
|
||||
attrs.setdefault('step', 'any')
|
||||
return attrs
|
||||
|
||||
|
||||
class DecimalField(IntegerField):
|
||||
default_error_messages = {
|
||||
'invalid': _('Enter a number.'),
|
||||
'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'),
|
||||
'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'),
|
||||
'max_digits': _('Ensure that there are no more than %s digits in total.'),
|
||||
'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
|
||||
'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
|
||||
}
|
||||
|
||||
def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
|
||||
self.max_value, self.min_value = max_value, min_value
|
||||
self.max_digits, self.decimal_places = max_digits, decimal_places
|
||||
Field.__init__(self, *args, **kwargs)
|
||||
|
||||
if max_value is not None:
|
||||
self.validators.append(validators.MaxValueValidator(max_value))
|
||||
if min_value is not None:
|
||||
self.validators.append(validators.MinValueValidator(min_value))
|
||||
super(DecimalField, self).__init__(max_value, min_value, *args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
"""
|
||||
|
@ -345,6 +355,19 @@ class DecimalField(Field):
|
|||
raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
|
||||
return value
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
attrs = super(DecimalField, self).widget_attrs(widget)
|
||||
if isinstance(widget, NumberInput):
|
||||
if self.max_digits is not None:
|
||||
max_length = self.max_digits + 1 # for the sign
|
||||
if self.decimal_places is None or self.decimal_places > 0:
|
||||
max_length += 1 # for the dot
|
||||
attrs['maxlength'] = max_length
|
||||
if self.decimal_places:
|
||||
attrs['step'] = '0.%s1' % ('0' * (self.decimal_places-1))
|
||||
return attrs
|
||||
|
||||
|
||||
class BaseTemporalField(Field):
|
||||
|
||||
def __init__(self, input_formats=None, *args, **kwargs):
|
||||
|
|
|
@ -23,7 +23,7 @@ from django.utils import datetime_safe, formats, six
|
|||
|
||||
__all__ = (
|
||||
'Media', 'MediaDefiningClass', 'Widget', 'TextInput',
|
||||
'EmailInput', 'URLInput', 'PasswordInput',
|
||||
'EmailInput', 'URLInput', 'NumberInput', 'PasswordInput',
|
||||
'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput',
|
||||
'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
|
||||
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
|
||||
|
@ -252,6 +252,10 @@ class TextInput(Input):
|
|||
super(TextInput, self).__init__(attrs)
|
||||
|
||||
|
||||
class NumberInput(TextInput):
|
||||
input_type = 'number'
|
||||
|
||||
|
||||
class EmailInput(TextInput):
|
||||
input_type = 'email'
|
||||
|
||||
|
|
|
@ -454,7 +454,8 @@ For each field, we describe the default widget used if you don't specify
|
|||
|
||||
.. class:: DecimalField(**kwargs)
|
||||
|
||||
* Default widget: :class:`TextInput`
|
||||
* Default widget: :class:`NumberInput` when :attr:`Field.localize` is
|
||||
``False``, else :class:`TextInput`.
|
||||
* Empty value: ``None``
|
||||
* Normalizes to: A Python ``decimal``.
|
||||
* Validates that the given value is a decimal. Leading and trailing
|
||||
|
@ -580,7 +581,8 @@ For each field, we describe the default widget used if you don't specify
|
|||
|
||||
.. class:: FloatField(**kwargs)
|
||||
|
||||
* Default widget: :class:`TextInput`
|
||||
* Default widget: :class:`NumberInput` when :attr:`Field.localize` is
|
||||
``False``, else :class:`TextInput`.
|
||||
* Empty value: ``None``
|
||||
* Normalizes to: A Python float.
|
||||
* Validates that the given value is an float. Leading and trailing
|
||||
|
@ -621,7 +623,8 @@ For each field, we describe the default widget used if you don't specify
|
|||
|
||||
.. class:: IntegerField(**kwargs)
|
||||
|
||||
* Default widget: :class:`TextInput`
|
||||
* Default widget: :class:`NumberInput` when :attr:`Field.localize` is
|
||||
``False``, else :class:`TextInput`.
|
||||
* Empty value: ``None``
|
||||
* Normalizes to: A Python integer or long integer.
|
||||
* Validates that the given value is an integer. Leading and trailing
|
||||
|
|
|
@ -389,6 +389,19 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
|||
|
||||
Text input: ``<input type="text" ...>``
|
||||
|
||||
``NumberInput``
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: NumberInput
|
||||
|
||||
.. versionadded:: 1.6
|
||||
|
||||
Text input: ``<input type="number" ...>``
|
||||
|
||||
Beware that not all browsers support entering localized numbers in
|
||||
``number`` input types. Django itself avoids using them for fields having
|
||||
their :attr:`~django.forms.Field.localize` property to ``True``.
|
||||
|
||||
``EmailInput``
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -60,9 +60,13 @@ Minor features
|
|||
* In addition to :lookup:`year`, :lookup:`month` and :lookup:`day`, the ORM
|
||||
now supports :lookup:`hour`, :lookup:`minute` and :lookup:`second` lookups.
|
||||
|
||||
* The default widgets for :class:`~django.forms.EmailField` and
|
||||
:class:`~django.forms.URLField` use the new type attributes available in
|
||||
HTML5 (type='email', type='url').
|
||||
* The default widgets for :class:`~django.forms.EmailField`,
|
||||
:class:`~django.forms.URLField`, :class:`~django.forms.IntegerField`,
|
||||
:class:`~django.forms.FloatField` and :class:`~django.forms.DecimalField` use
|
||||
the new type attributes available in HTML5 (type='email', type='url',
|
||||
type='number'). Note that due to erratic support of the ``number`` input type
|
||||
with localized numbers in current browsers, Django only uses it when numeric
|
||||
fields are not localized.
|
||||
|
||||
* The ``number`` argument for :ref:`lazy plural translations
|
||||
<lazy-plural-translations>` can be provided at translation time rather than
|
||||
|
@ -122,7 +126,8 @@ Backwards incompatible changes in 1.6
|
|||
|
||||
* 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
|
||||
``type='email'`` or ``type='url'`` depending on their corresponding field type.
|
||||
``type='email'``, ``type='url'`` or ``type='number'`` depending on their
|
||||
corresponding field type.
|
||||
|
||||
* Extraction of translatable literals from templates with the
|
||||
:djadmin:`makemessages` command now correctly detects i18n constructs when
|
||||
|
|
|
@ -273,13 +273,13 @@ Lets you create a formset with the ability to order::
|
|||
... print(form.as_table())
|
||||
<tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr>
|
||||
<tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="text" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
|
||||
<tr><th><label for="id_form-0-ORDER">Order:</label></th><td><input type="number" name="form-0-ORDER" value="1" id="id_form-0-ORDER" /></td></tr>
|
||||
<tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" value="Article #2" id="id_form-1-title" /></td></tr>
|
||||
<tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" value="2008-05-11" id="id_form-1-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="text" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
|
||||
<tr><th><label for="id_form-1-ORDER">Order:</label></th><td><input type="number" name="form-1-ORDER" value="2" id="id_form-1-ORDER" /></td></tr>
|
||||
<tr><th><label for="id_form-2-title">Title:</label></th><td><input type="text" name="form-2-title" id="id_form-2-title" /></td></tr>
|
||||
<tr><th><label for="id_form-2-pub_date">Pub date:</label></th><td><input type="text" name="form-2-pub_date" id="id_form-2-pub_date" /></td></tr>
|
||||
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="text" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>
|
||||
<tr><th><label for="id_form-2-ORDER">Order:</label></th><td><input type="number" name="form-2-ORDER" id="id_form-2-ORDER" /></td></tr>
|
||||
|
||||
This adds an additional field to each form. This new field is named ``ORDER``
|
||||
and is an ``forms.IntegerField``. For the forms that came from the initial
|
||||
|
|
|
@ -1133,7 +1133,7 @@ class OldFormForXTests(TestCase):
|
|||
<option value="%s">Joe Better</option>
|
||||
<option value="%s">Mike Royko</option>
|
||||
</select></p>
|
||||
<p><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk))
|
||||
<p><label for="id_age">Age:</label> <input type="number" name="age" id="id_age" min="0" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk))
|
||||
|
||||
data = {
|
||||
'writer': six.text_type(w_woodward.pk),
|
||||
|
@ -1151,7 +1151,7 @@ class OldFormForXTests(TestCase):
|
|||
<option value="%s">Joe Better</option>
|
||||
<option value="%s">Mike Royko</option>
|
||||
</select></p>
|
||||
<p><label for="id_age">Age:</label> <input type="text" name="age" value="65" id="id_age" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk))
|
||||
<p><label for="id_age">Age:</label> <input type="number" name="age" value="65" id="id_age" min="0" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk))
|
||||
|
||||
def test_file_field(self):
|
||||
# Test conditions when files is either not given or empty.
|
||||
|
|
|
@ -392,7 +392,7 @@ class ModelFormsetTest(TestCase):
|
|||
self.assertEqual(len(formset.forms), 1)
|
||||
self.assertHTMLEqual(formset.forms[0].as_p(),
|
||||
'<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" maxlength="100" /></p>\n'
|
||||
'<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" id="id_form-0-author_ptr" /></p>')
|
||||
'<p><label for="id_form-0-write_speed">Write speed:</label> <input type="number" name="form-0-write_speed" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" id="id_form-0-author_ptr" /></p>')
|
||||
|
||||
data = {
|
||||
'form-TOTAL_FORMS': '1', # the number of forms rendered
|
||||
|
@ -415,10 +415,10 @@ class ModelFormsetTest(TestCase):
|
|||
self.assertEqual(len(formset.forms), 2)
|
||||
self.assertHTMLEqual(formset.forms[0].as_p(),
|
||||
'<p><label for="id_form-0-name">Name:</label> <input id="id_form-0-name" type="text" name="form-0-name" value="Ernest Hemingway" maxlength="100" /></p>\n'
|
||||
'<p><label for="id_form-0-write_speed">Write speed:</label> <input type="text" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" value="%d" id="id_form-0-author_ptr" /></p>' % hemingway_id)
|
||||
'<p><label for="id_form-0-write_speed">Write speed:</label> <input type="number" name="form-0-write_speed" value="10" id="id_form-0-write_speed" /><input type="hidden" name="form-0-author_ptr" value="%d" id="id_form-0-author_ptr" /></p>' % hemingway_id)
|
||||
self.assertHTMLEqual(formset.forms[1].as_p(),
|
||||
'<p><label for="id_form-1-name">Name:</label> <input id="id_form-1-name" type="text" name="form-1-name" maxlength="100" /></p>\n'
|
||||
'<p><label for="id_form-1-write_speed">Write speed:</label> <input type="text" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr" id="id_form-1-author_ptr" /></p>')
|
||||
'<p><label for="id_form-1-write_speed">Write speed:</label> <input type="number" name="form-1-write_speed" id="id_form-1-write_speed" /><input type="hidden" name="form-1-author_ptr" id="id_form-1-author_ptr" /></p>')
|
||||
|
||||
data = {
|
||||
'form-TOTAL_FORMS': '2', # the number of forms rendered
|
||||
|
@ -551,6 +551,7 @@ class ModelFormsetTest(TestCase):
|
|||
def test_inline_formsets_with_custom_pk(self):
|
||||
# Test inline formsets where the inline-edited object has a custom
|
||||
# primary key that is not the fk to the parent object.
|
||||
self.maxDiff = 1024
|
||||
|
||||
AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1)
|
||||
author = Author.objects.create(pk=1, name='Charles Baudelaire')
|
||||
|
@ -558,7 +559,7 @@ class ModelFormsetTest(TestCase):
|
|||
formset = AuthorBooksFormSet2(instance=author)
|
||||
self.assertEqual(len(formset.forms), 1)
|
||||
self.assertHTMLEqual(formset.forms[0].as_p(),
|
||||
'<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input type="text" name="bookwithcustompk_set-0-my_pk" id="id_bookwithcustompk_set-0-my_pk" /></p>\n'
|
||||
'<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input id="id_bookwithcustompk_set-0-my_pk" type="number" name="bookwithcustompk_set-0-my_pk" maxlength="6" /></p>\n'
|
||||
'<p><label for="id_bookwithcustompk_set-0-title">Title:</label> <input id="id_bookwithcustompk_set-0-title" type="text" name="bookwithcustompk_set-0-title" maxlength="100" /><input type="hidden" name="bookwithcustompk_set-0-author" value="1" id="id_bookwithcustompk_set-0-author" /></p>')
|
||||
|
||||
data = {
|
||||
|
@ -806,7 +807,7 @@ class ModelFormsetTest(TestCase):
|
|||
'<option value="%d">Joe Perry at Giordanos</option>\n'
|
||||
'<option value="%d">Jack Berry at Giordanos</option>\n'
|
||||
'</select></p>\n'
|
||||
'<p><label for="id_form-0-age">Age:</label> <input type="text" name="form-0-age" id="id_form-0-age" /></p>'
|
||||
'<p><label for="id_form-0-age">Age:</label> <input type="number" name="form-0-age" id="id_form-0-age" min="0" /></p>'
|
||||
% (owner1.auto_id, owner2.auto_id))
|
||||
|
||||
owner1 = Owner.objects.get(name='Joe Perry')
|
||||
|
@ -816,7 +817,7 @@ class ModelFormsetTest(TestCase):
|
|||
formset = FormSet(instance=owner1)
|
||||
self.assertEqual(len(formset.forms), 1)
|
||||
self.assertHTMLEqual(formset.forms[0].as_p(),
|
||||
'<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>'
|
||||
'<p><label for="id_ownerprofile-0-age">Age:</label> <input type="number" name="ownerprofile-0-age" id="id_ownerprofile-0-age" min="0" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>'
|
||||
% owner1.auto_id)
|
||||
|
||||
data = {
|
||||
|
@ -837,7 +838,7 @@ class ModelFormsetTest(TestCase):
|
|||
formset = FormSet(instance=owner1)
|
||||
self.assertEqual(len(formset.forms), 1)
|
||||
self.assertHTMLEqual(formset.forms[0].as_p(),
|
||||
'<p><label for="id_ownerprofile-0-age">Age:</label> <input type="text" name="ownerprofile-0-age" value="54" id="id_ownerprofile-0-age" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>'
|
||||
'<p><label for="id_ownerprofile-0-age">Age:</label> <input type="number" name="ownerprofile-0-age" value="54" id="id_ownerprofile-0-age" min="0" /><input type="hidden" name="ownerprofile-0-owner" value="%d" id="id_ownerprofile-0-owner" /></p>'
|
||||
% owner1.auto_id)
|
||||
|
||||
data = {
|
||||
|
@ -993,7 +994,7 @@ class ModelFormsetTest(TestCase):
|
|||
result = re.sub(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?', '__DATETIME__', result)
|
||||
self.assertHTMLEqual(result,
|
||||
'<p><label for="id_membership_set-0-date_joined">Date joined:</label> <input type="text" name="membership_set-0-date_joined" value="__DATETIME__" id="id_membership_set-0-date_joined" /><input type="hidden" name="initial-membership_set-0-date_joined" value="__DATETIME__" id="initial-membership_set-0-id_membership_set-0-date_joined" /></p>\n'
|
||||
'<p><label for="id_membership_set-0-karma">Karma:</label> <input type="text" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="%d" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p>'
|
||||
'<p><label for="id_membership_set-0-karma">Karma:</label> <input type="number" name="membership_set-0-karma" id="id_membership_set-0-karma" /><input type="hidden" name="membership_set-0-person" value="%d" id="id_membership_set-0-person" /><input type="hidden" name="membership_set-0-id" id="id_membership_set-0-id" /></p>'
|
||||
% person.id)
|
||||
|
||||
# test for validation with callable defaults. Validations rely on hidden fields
|
||||
|
|
|
@ -35,7 +35,6 @@ from decimal import Decimal
|
|||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from django.forms import *
|
||||
from django.test import SimpleTestCase
|
||||
from django.utils import formats
|
||||
from django.utils import six
|
||||
from django.utils._os import upath
|
||||
|
||||
|
@ -131,6 +130,7 @@ class FieldsTests(SimpleTestCase):
|
|||
|
||||
def test_integerfield_1(self):
|
||||
f = IntegerField()
|
||||
self.assertWidgetRendersTo(f, '<input type="number" name="f" id="id_f" />')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
|
||||
self.assertEqual(1, f.clean('1'))
|
||||
|
@ -165,6 +165,7 @@ class FieldsTests(SimpleTestCase):
|
|||
|
||||
def test_integerfield_3(self):
|
||||
f = IntegerField(max_value=10)
|
||||
self.assertWidgetRendersTo(f, '<input max="10" type="number" name="f" id="id_f" />')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
|
||||
self.assertEqual(1, f.clean(1))
|
||||
self.assertEqual(10, f.clean(10))
|
||||
|
@ -176,6 +177,7 @@ class FieldsTests(SimpleTestCase):
|
|||
|
||||
def test_integerfield_4(self):
|
||||
f = IntegerField(min_value=10)
|
||||
self.assertWidgetRendersTo(f, '<input id="id_f" type="number" name="f" min="10" />')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
|
||||
self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'", f.clean, 1)
|
||||
self.assertEqual(10, f.clean(10))
|
||||
|
@ -187,6 +189,7 @@ class FieldsTests(SimpleTestCase):
|
|||
|
||||
def test_integerfield_5(self):
|
||||
f = IntegerField(min_value=10, max_value=20)
|
||||
self.assertWidgetRendersTo(f, '<input id="id_f" max="20" type="number" name="f" min="10" />')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
|
||||
self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'", f.clean, 1)
|
||||
self.assertEqual(10, f.clean(10))
|
||||
|
@ -198,10 +201,19 @@ class FieldsTests(SimpleTestCase):
|
|||
self.assertEqual(f.max_value, 20)
|
||||
self.assertEqual(f.min_value, 10)
|
||||
|
||||
def test_integerfield_localized(self):
|
||||
"""
|
||||
Make sure localized IntegerField's widget renders to a text input with
|
||||
no number input specific attributes.
|
||||
"""
|
||||
f1 = IntegerField(localize=True)
|
||||
self.assertWidgetRendersTo(f1, '<input id="id_f" name="f" type="text" />')
|
||||
|
||||
# FloatField ##################################################################
|
||||
|
||||
def test_floatfield_1(self):
|
||||
f = FloatField()
|
||||
self.assertWidgetRendersTo(f, '<input step="any" type="number" name="f" id="id_f" />')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
|
||||
self.assertEqual(1.0, f.clean('1'))
|
||||
|
@ -228,6 +240,7 @@ class FieldsTests(SimpleTestCase):
|
|||
|
||||
def test_floatfield_3(self):
|
||||
f = FloatField(max_value=1.5, min_value=0.5)
|
||||
self.assertWidgetRendersTo(f, '<input step="any" name="f" min="0.5" max="1.5" type="number" id="id_f" />')
|
||||
self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'", f.clean, '1.6')
|
||||
self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'", f.clean, '0.4')
|
||||
self.assertEqual(1.5, f.clean('1.5'))
|
||||
|
@ -235,10 +248,19 @@ class FieldsTests(SimpleTestCase):
|
|||
self.assertEqual(f.max_value, 1.5)
|
||||
self.assertEqual(f.min_value, 0.5)
|
||||
|
||||
def test_floatfield_localized(self):
|
||||
"""
|
||||
Make sure localized FloatField's widget renders to a text input with
|
||||
no number input specific attributes.
|
||||
"""
|
||||
f = FloatField(localize=True)
|
||||
self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />')
|
||||
|
||||
# DecimalField ################################################################
|
||||
|
||||
def test_decimalfield_1(self):
|
||||
f = DecimalField(max_digits=4, decimal_places=2)
|
||||
self.assertWidgetRendersTo(f, '<input id="id_f" step="0.01" type="number" name="f" maxlength="6" />')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
|
||||
self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
|
||||
self.assertEqual(f.clean('1'), Decimal("1"))
|
||||
|
@ -284,6 +306,7 @@ class FieldsTests(SimpleTestCase):
|
|||
|
||||
def test_decimalfield_3(self):
|
||||
f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
|
||||
self.assertWidgetRendersTo(f, '<input step="0.01" name="f" min="0.5" max="1.5" maxlength="6" type="number" id="id_f" />')
|
||||
self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'", f.clean, '1.6')
|
||||
self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'", f.clean, '0.4')
|
||||
self.assertEqual(f.clean('1.5'), Decimal("1.5"))
|
||||
|
@ -315,6 +338,14 @@ class FieldsTests(SimpleTestCase):
|
|||
self.assertEqual(f.clean('.01'), Decimal(".01"))
|
||||
self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 0 digits before the decimal point.'", f.clean, '1.1')
|
||||
|
||||
def test_decimalfield_localized(self):
|
||||
"""
|
||||
Make sure localized DecimalField's widget renders to a text input with
|
||||
no number input specific attributes.
|
||||
"""
|
||||
f = DecimalField(localize=True)
|
||||
self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />')
|
||||
|
||||
# DateField ###################################################################
|
||||
|
||||
def test_datefield_1(self):
|
||||
|
|
|
@ -1740,7 +1740,7 @@ class FormsTestCase(TestCase):
|
|||
<option value="3">No</option>
|
||||
</select></li>
|
||||
<li><label for="id_email">Email:</label> <input type="email" name="email" id="id_email" /></li>
|
||||
<li class="required error"><ul class="errorlist"><li>This field is required.</li></ul><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></li>""")
|
||||
<li class="required error"><ul class="errorlist"><li>This field is required.</li></ul><label for="id_age">Age:</label> <input type="number" name="age" id="id_age" /></li>""")
|
||||
|
||||
self.assertHTMLEqual(p.as_p(), """<ul class="errorlist"><li>This field is required.</li></ul>
|
||||
<p class="required error"><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
|
||||
|
@ -1751,7 +1751,7 @@ class FormsTestCase(TestCase):
|
|||
</select></p>
|
||||
<p><label for="id_email">Email:</label> <input type="email" name="email" id="id_email" /></p>
|
||||
<ul class="errorlist"><li>This field is required.</li></ul>
|
||||
<p class="required error"><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></p>""")
|
||||
<p class="required error"><label for="id_age">Age:</label> <input type="number" name="age" id="id_age" /></p>""")
|
||||
|
||||
self.assertHTMLEqual(p.as_table(), """<tr class="required error"><th><label for="id_name">Name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="name" id="id_name" /></td></tr>
|
||||
<tr class="required"><th><label for="id_is_cool">Is cool:</label></th><td><select name="is_cool" id="id_is_cool">
|
||||
|
@ -1760,7 +1760,7 @@ class FormsTestCase(TestCase):
|
|||
<option value="3">No</option>
|
||||
</select></td></tr>
|
||||
<tr><th><label for="id_email">Email:</label></th><td><input type="email" name="email" id="id_email" /></td></tr>
|
||||
<tr class="required error"><th><label for="id_age">Age:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="age" id="id_age" /></td></tr>""")
|
||||
<tr class="required error"><th><label for="id_age">Age:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="number" name="age" id="id_age" /></td></tr>""")
|
||||
|
||||
def test_label_split_datetime_not_displayed(self):
|
||||
class EventForm(Form):
|
||||
|
|
|
@ -53,7 +53,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
formset = ChoiceFormSet(auto_id=False, prefix='choices')
|
||||
self.assertHTMLEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="1000" />
|
||||
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
|
||||
<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>""")
|
||||
<tr><th>Votes:</th><td><input type="number" name="choices-0-votes" /></td></tr>""")
|
||||
|
||||
# On thing to note is that there needs to be a special value in the data. This
|
||||
# value tells the FormSet how many forms were displayed so it can tell how
|
||||
|
@ -137,9 +137,9 @@ class FormsFormsetTestCase(TestCase):
|
|||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
|
||||
<li>Choice: <input type="text" name="choices-1-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-1-votes" /></li>""")
|
||||
<li>Votes: <input type="number" name="choices-1-votes" /></li>""")
|
||||
|
||||
# Let's simulate what would happen if we submitted this form.
|
||||
|
||||
|
@ -210,11 +210,11 @@ class FormsFormsetTestCase(TestCase):
|
|||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" /></li>
|
||||
<li>Choice: <input type="text" name="choices-1-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-1-votes" /></li>
|
||||
<li>Votes: <input type="number" name="choices-1-votes" /></li>
|
||||
<li>Choice: <input type="text" name="choices-2-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-2-votes" /></li>""")
|
||||
<li>Votes: <input type="number" name="choices-2-votes" /></li>""")
|
||||
|
||||
# Since we displayed every form as blank, we will also accept them back as blank.
|
||||
# This may seem a little strange, but later we will show how to require a minimum
|
||||
|
@ -301,19 +301,19 @@ class FormsFormsetTestCase(TestCase):
|
|||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
|
||||
<li>Choice: <input type="text" name="choices-1-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-1-votes" /></li>
|
||||
<li>Votes: <input type="number" name="choices-1-votes" /></li>
|
||||
<li>Choice: <input type="text" name="choices-2-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-2-votes" /></li>
|
||||
<li>Votes: <input type="number" name="choices-2-votes" /></li>
|
||||
<li>Choice: <input type="text" name="choices-3-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-3-votes" /></li>""")
|
||||
<li>Votes: <input type="number" name="choices-3-votes" /></li>""")
|
||||
|
||||
# Make sure retrieving an empty form works, and it shows up in the form list
|
||||
|
||||
self.assertTrue(formset.empty_form.empty_permitted)
|
||||
self.assertHTMLEqual(formset.empty_form.as_ul(), """<li>Choice: <input type="text" name="choices-__prefix__-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-__prefix__-votes" /></li>""")
|
||||
<li>Votes: <input type="number" name="choices-__prefix__-votes" /></li>""")
|
||||
|
||||
def test_formset_with_deletion(self):
|
||||
# FormSets with deletion ######################################################
|
||||
|
@ -331,13 +331,13 @@ class FormsFormsetTestCase(TestCase):
|
|||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
|
||||
<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
|
||||
<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
|
||||
<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
|
||||
<li>Votes: <input type="number" name="choices-1-votes" value="900" /></li>
|
||||
<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
|
||||
<li>Choice: <input type="text" name="choices-2-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-2-votes" /></li>
|
||||
<li>Votes: <input type="number" name="choices-2-votes" /></li>
|
||||
<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>""")
|
||||
|
||||
# To delete something, we just need to set that form's special delete field to
|
||||
|
@ -428,14 +428,14 @@ class FormsFormsetTestCase(TestCase):
|
|||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
|
||||
<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
|
||||
<li>Order: <input type="number" name="choices-0-ORDER" value="1" /></li>
|
||||
<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
|
||||
<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
|
||||
<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
|
||||
<li>Votes: <input type="number" name="choices-1-votes" value="900" /></li>
|
||||
<li>Order: <input type="number" name="choices-1-ORDER" value="2" /></li>
|
||||
<li>Choice: <input type="text" name="choices-2-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-2-votes" /></li>
|
||||
<li>Order: <input type="text" name="choices-2-ORDER" /></li>""")
|
||||
<li>Votes: <input type="number" name="choices-2-votes" /></li>
|
||||
<li>Order: <input type="number" name="choices-2-ORDER" /></li>""")
|
||||
|
||||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
|
@ -539,20 +539,20 @@ class FormsFormsetTestCase(TestCase):
|
|||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
|
||||
<li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
|
||||
<li>Order: <input type="number" name="choices-0-ORDER" value="1" /></li>
|
||||
<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
|
||||
<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
|
||||
<li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
|
||||
<li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
|
||||
<li>Votes: <input type="number" name="choices-1-votes" value="900" /></li>
|
||||
<li>Order: <input type="number" name="choices-1-ORDER" value="2" /></li>
|
||||
<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
|
||||
<li>Choice: <input type="text" name="choices-2-choice" value="The Decemberists" /></li>
|
||||
<li>Votes: <input type="text" name="choices-2-votes" value="500" /></li>
|
||||
<li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
|
||||
<li>Votes: <input type="number" name="choices-2-votes" value="500" /></li>
|
||||
<li>Order: <input type="number" name="choices-2-ORDER" value="3" /></li>
|
||||
<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
|
||||
<li>Choice: <input type="text" name="choices-3-choice" /></li>
|
||||
<li>Votes: <input type="text" name="choices-3-votes" /></li>
|
||||
<li>Order: <input type="text" name="choices-3-ORDER" /></li>
|
||||
<li>Votes: <input type="number" name="choices-3-votes" /></li>
|
||||
<li>Order: <input type="number" name="choices-3-ORDER" /></li>
|
||||
<li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li>""")
|
||||
|
||||
# Let's delete Fergie, and put The Decemberists ahead of Calexico.
|
||||
|
@ -956,19 +956,19 @@ class FormsetAsFooTests(TestCase):
|
|||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertHTMLEqual(formset.as_table(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
|
||||
<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" value="100" /></td></tr>""")
|
||||
<tr><th>Votes:</th><td><input type="number" name="choices-0-votes" value="100" /></td></tr>""")
|
||||
|
||||
def test_as_p(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertHTMLEqual(formset.as_p(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
|
||||
<p>Votes: <input type="text" name="choices-0-votes" value="100" /></p>""")
|
||||
<p>Votes: <input type="number" name="choices-0-votes" value="100" /></p>""")
|
||||
|
||||
def test_as_ul(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertHTMLEqual(formset.as_ul(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
|
||||
<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>""")
|
||||
|
||||
|
||||
# Regression test for #11418 #################################################
|
||||
|
|
|
@ -651,6 +651,7 @@ class FormattingTests(TestCase):
|
|||
"""
|
||||
Tests if form input is correctly localized
|
||||
"""
|
||||
self.maxDiff = 1200
|
||||
with translation.override('de-at', deactivate=True):
|
||||
form6 = CompanyForm({
|
||||
'name': 'acme',
|
||||
|
|
Loading…
Reference in New Issue