Ticket #19686: ticket-19686-claudep-github-revised.diff

File ticket-19686-claudep-github-revised.diff, 38.5 KB (added by charettes, 3 years ago)
  • django/forms/fields.py

    diff --git a/django/forms/fields.py b/django/forms/fields.py
    index c547d14..180b524 100644
    a b from django.core import validators 
    1919from django.core.exceptions import ValidationError
    2020from django.forms.util import ErrorList, from_current_timezone, to_current_timezone
    2121from django.forms.widgets import (
    22     TextInput, PasswordInput, EmailInput, URLInput, HiddenInput,
     22    TextInput, NumberInput, EmailInput, URLInput, HiddenInput,
    2323    MultipleHiddenInput, ClearableFileInput, CheckboxInput, Select,
    2424    NullBooleanSelect, SelectMultiple, DateInput, DateTimeInput, TimeInput,
    2525    SplitDateTimeWidget, SplitHiddenDateTimeWidget, FILE_INPUT_CONTRADICTION
    class IntegerField(Field): 
    234234
    235235    def __init__(self, max_value=None, min_value=None, *args, **kwargs):
    236236        self.max_value, self.min_value = max_value, min_value
     237        kwargs.setdefault('widget', NumberInput if not kwargs.get('localize') else self.widget)
    237238        super(IntegerField, self).__init__(*args, **kwargs)
    238239
    239240        if max_value is not None:
    class IntegerField(Field): 
    257258            raise ValidationError(self.error_messages['invalid'])
    258259        return value
    259260
     261    def widget_attrs(self, widget):
     262        attrs = super(IntegerField, self).widget_attrs(widget)
     263        if isinstance(widget, NumberInput):
     264            if self.min_value is not None:
     265                attrs['min'] = self.min_value
     266            if self.max_value is not None:
     267                attrs['max'] = self.max_value
     268        return attrs
     269
     270
    260271class FloatField(IntegerField):
    261272    default_error_messages = {
    262273        'invalid': _('Enter a number.'),
    class FloatField(IntegerField): 
    278289            raise ValidationError(self.error_messages['invalid'])
    279290        return value
    280291
    281 class DecimalField(Field):
     292    def widget_attrs(self, widget):
     293        attrs = super(FloatField, self).widget_attrs(widget)
     294        if isinstance(widget, NumberInput):
     295            attrs.setdefault('step', 'any')
     296        return attrs
     297
     298
     299class DecimalField(IntegerField):
    282300    default_error_messages = {
    283301        'invalid': _('Enter a number.'),
    284         'max_value': _('Ensure this value is less than or equal to %(limit_value)s.'),
    285         'min_value': _('Ensure this value is greater than or equal to %(limit_value)s.'),
    286302        'max_digits': _('Ensure that there are no more than %s digits in total.'),
    287303        'max_decimal_places': _('Ensure that there are no more than %s decimal places.'),
    288304        'max_whole_digits': _('Ensure that there are no more than %s digits before the decimal point.')
    289305    }
    290306
    291307    def __init__(self, max_value=None, min_value=None, max_digits=None, decimal_places=None, *args, **kwargs):
    292         self.max_value, self.min_value = max_value, min_value
    293308        self.max_digits, self.decimal_places = max_digits, decimal_places
    294         Field.__init__(self, *args, **kwargs)
    295 
    296         if max_value is not None:
    297             self.validators.append(validators.MaxValueValidator(max_value))
    298         if min_value is not None:
    299             self.validators.append(validators.MinValueValidator(min_value))
     309        super(DecimalField, self).__init__(max_value, min_value, *args, **kwargs)
    300310
    301311    def to_python(self, value):
    302312        """
    class DecimalField(Field): 
    345355            raise ValidationError(self.error_messages['max_whole_digits'] % (self.max_digits - self.decimal_places))
    346356        return value
    347357
     358    def widget_attrs(self, widget):
     359        attrs = super(DecimalField, self).widget_attrs(widget)
     360        if isinstance(widget, NumberInput):
     361            if self.max_digits is not None:
     362                max_length = self.max_digits + 1 # for the sign
     363                if self.decimal_places is None or self.decimal_places > 0:
     364                    max_length += 1 # for the dot
     365                attrs['maxlength'] = max_length
     366            if self.decimal_places:
     367                attrs['step'] = '0.%s1' % ('0' * (self.decimal_places-1))
     368        return attrs
     369
     370
    348371class BaseTemporalField(Field):
    349372
    350373    def __init__(self, input_formats=None, *args, **kwargs):
  • django/forms/widgets.py

    diff --git a/django/forms/widgets.py b/django/forms/widgets.py
    index e906ed5..026e8dc 100644
    a b from django.utils import datetime_safe, formats, six 
    2323
    2424__all__ = (
    2525    'Media', 'MediaDefiningClass', 'Widget', 'TextInput',
    26     'EmailInput', 'URLInput', 'PasswordInput',
     26    'EmailInput', 'URLInput', 'NumberInput', 'PasswordInput',
    2727    'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput',
    2828    'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
    2929    'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
    class TextInput(Input): 
    252252        super(TextInput, self).__init__(attrs)
    253253
    254254
     255class NumberInput(TextInput):
     256    input_type = 'number'
     257
     258
    255259class EmailInput(TextInput):
    256260    input_type = 'email'
    257261
  • docs/ref/forms/fields.txt

    diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
    index 2e4e779..85650ad 100644
    a b For each field, we describe the default widget used if you don't specify 
    454454
    455455.. class:: DecimalField(**kwargs)
    456456
    457     * Default widget: :class:`TextInput`
     457    * Default widget: :class:`NumberInput` when :attr:`Field.localize` is
     458      ``False``, else :class:`TextInput`.
    458459    * Empty value: ``None``
    459460    * Normalizes to: A Python ``decimal``.
    460461    * Validates that the given value is a decimal. Leading and trailing
    For each field, we describe the default widget used if you don't specify 
    580581
    581582.. class:: FloatField(**kwargs)
    582583
    583     * Default widget: :class:`TextInput`
     584    * Default widget: :class:`NumberInput` when :attr:`Field.localize` is
     585      ``False``, else :class:`TextInput`.
    584586    * Empty value: ``None``
    585587    * Normalizes to: A Python float.
    586588    * Validates that the given value is an float. Leading and trailing
    For each field, we describe the default widget used if you don't specify 
    621623
    622624.. class:: IntegerField(**kwargs)
    623625
    624     * Default widget: :class:`TextInput`
     626    * Default widget: :class:`NumberInput` when :attr:`Field.localize` is
     627      ``False``, else :class:`TextInput`.
    625628    * Empty value: ``None``
    626629    * Normalizes to: A Python integer or long integer.
    627630    * Validates that the given value is an integer. Leading and trailing
  • docs/ref/forms/widgets.txt

    diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
    index cb5224f..fdff0bf 100644
    a b These widgets make use of the HTML elements ``input`` and ``textarea``. 
    394394
    395395    Text input: ``<input type="text" ...>``
    396396
     397``NumberInput``
     398~~~~~~~~~~~~~~~
     399
     400.. class:: NumberInput
     401
     402    .. versionadded:: 1.6
     403
     404    Text input: ``<input type="number" ...>``
     405
     406    Beware that not all browsers support entering localized numbers in
     407    ``number`` input types. Django itself avoids using them for fields having
     408    their :attr:`~django.forms.Field.localize` property to ``True``.
     409
    397410``EmailInput``
    398411~~~~~~~~~~~~~~
    399412
  • docs/releases/1.6.txt

    diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
    index 32e5172..dadc8c9 100644
    a b Minor features 
    4444* Added :meth:`~django.db.models.query.QuerySet.earliest` for symmetry with
    4545  :meth:`~django.db.models.query.QuerySet.latest`.
    4646
    47 * The default widgets for :class:`~django.forms.EmailField` and
    48   :class:`~django.forms.URLField` use the new type attributes available in
    49   HTML5 (type='email', type='url').
     47* The default widgets for :class:`~django.forms.EmailField`,
     48  :class:`~django.forms.URLField`, :class:`~django.forms.IntegerField`,
     49  :class:`~django.forms.FloatField` and :class:`~django.forms.DecimalField` use
     50  the new type attributes available in HTML5 (type='email', type='url',
     51  type='number'). Note that due to erratic support of the ``number`` input type
     52  with localized numbers in current browsers, Django only uses it when numeric
     53  fields are not localized.
    5054
    5155* The ``number`` argument for :ref:`lazy plural translations
    5256  <lazy-plural-translations>` can be provided at translation time rather than
    Backwards incompatible changes in 1.6 
    7175
    7276* If your CSS/Javascript code used to access HTML input widgets by type, you
    7377  should review it as ``type='text'`` widgets might be now output as
    74   ``type='email'`` or ``type='url'`` depending on their corresponding field type.
     78  ``type='email'``, ``type='url'`` or ``type='number'`` depending on their
     79  corresponding field type.
    7580
    7681* Extraction of translatable literals from templates with the
    7782  :djadmin:`makemessages` command now correctly detects i18n constructs when
  • docs/topics/forms/formsets.txt

    diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt
    index ee1c69e..b3b5586 100644
    a b Lets you create a formset with the ability to order:: 
    273273    ...     print(form.as_table())
    274274    <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>
    275275    <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>
    276     <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>
     276    <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>
    277277    <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>
    278278    <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>
    279     <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>
     279    <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>
    280280    <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>
    281281    <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>
    282     <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>
     282    <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>
    283283
    284284This adds an additional field to each form. This new field is named ``ORDER``
    285285and is an ``forms.IntegerField``. For the forms that came from the initial
  • tests/modeltests/model_forms/tests.py

    diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py
    index 6ecd127..fb40037 100644
    a b class OldFormForXTests(TestCase): 
    11331133<option value="%s">Joe Better</option>
    11341134<option value="%s">Mike Royko</option>
    11351135</select></p>
    1136 <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))
     1136<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))
    11371137
    11381138        data = {
    11391139            'writer': six.text_type(w_woodward.pk),
    class OldFormForXTests(TestCase): 
    11511151<option value="%s">Joe Better</option>
    11521152<option value="%s">Mike Royko</option>
    11531153</select></p>
    1154 <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))
     1154<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))
    11551155
    11561156    def test_file_field(self):
    11571157        # Test conditions when files is either not given or empty.
  • tests/modeltests/model_formsets/tests.py

    diff --git a/tests/modeltests/model_formsets/tests.py b/tests/modeltests/model_formsets/tests.py
    index a028e65..91e1524 100644
    a b class ModelFormsetTest(TestCase): 
    384384        self.assertEqual(len(formset.forms), 1)
    385385        self.assertHTMLEqual(formset.forms[0].as_p(),
    386386            '<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'
    387             '<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>')
     387            '<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>')
    388388
    389389        data = {
    390390            'form-TOTAL_FORMS': '1', # the number of forms rendered
    class ModelFormsetTest(TestCase): 
    407407        self.assertEqual(len(formset.forms), 2)
    408408        self.assertHTMLEqual(formset.forms[0].as_p(),
    409409            '<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'
    410             '<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)
     410            '<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)
    411411        self.assertHTMLEqual(formset.forms[1].as_p(),
    412412            '<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'
    413             '<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>')
     413            '<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>')
    414414
    415415        data = {
    416416            'form-TOTAL_FORMS': '2', # the number of forms rendered
    class ModelFormsetTest(TestCase): 
    543543    def test_inline_formsets_with_custom_pk(self):
    544544        # Test inline formsets where the inline-edited object has a custom
    545545        # primary key that is not the fk to the parent object.
     546        self.maxDiff = 1024
    546547
    547548        AuthorBooksFormSet2 = inlineformset_factory(Author, BookWithCustomPK, can_delete=False, extra=1)
    548549        author = Author.objects.create(pk=1, name='Charles Baudelaire')
    class ModelFormsetTest(TestCase): 
    550551        formset = AuthorBooksFormSet2(instance=author)
    551552        self.assertEqual(len(formset.forms), 1)
    552553        self.assertHTMLEqual(formset.forms[0].as_p(),
    553             '<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'
     554            '<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'
    554555            '<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>')
    555556
    556557        data = {
    class ModelFormsetTest(TestCase): 
    798799            '<option value="%d">Joe Perry at Giordanos</option>\n'
    799800            '<option value="%d">Jack Berry at Giordanos</option>\n'
    800801            '</select></p>\n'
    801             '<p><label for="id_form-0-age">Age:</label> <input type="text" name="form-0-age" id="id_form-0-age" /></p>'
     802            '<p><label for="id_form-0-age">Age:</label> <input type="number" name="form-0-age" id="id_form-0-age" min="0" /></p>'
    802803            % (owner1.auto_id, owner2.auto_id))
    803804
    804805        owner1 = Owner.objects.get(name='Joe Perry')
    class ModelFormsetTest(TestCase): 
    808809        formset = FormSet(instance=owner1)
    809810        self.assertEqual(len(formset.forms), 1)
    810811        self.assertHTMLEqual(formset.forms[0].as_p(),
    811             '<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>'
     812            '<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>'
    812813            % owner1.auto_id)
    813814
    814815        data = {
    class ModelFormsetTest(TestCase): 
    829830        formset = FormSet(instance=owner1)
    830831        self.assertEqual(len(formset.forms), 1)
    831832        self.assertHTMLEqual(formset.forms[0].as_p(),
    832             '<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>'
     833            '<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>'
    833834            % owner1.auto_id)
    834835
    835836        data = {
    class ModelFormsetTest(TestCase): 
    985986        result = re.sub(r'\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)?', '__DATETIME__', result)
    986987        self.assertHTMLEqual(result,
    987988            '<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'
    988             '<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>'
     989            '<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>'
    989990            % person.id)
    990991
    991992        # test for validation with callable defaults. Validations rely on hidden fields
  • tests/regressiontests/forms/tests/fields.py

    diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py
    index fc7fc70..3fe2cd2 100644
    a b from decimal import Decimal 
    3535from django.core.files.uploadedfile import SimpleUploadedFile
    3636from django.forms import *
    3737from django.test import SimpleTestCase
    38 from django.utils import formats
    3938from django.utils import six
    4039from django.utils._os import upath
    4140
    class FieldsTests(SimpleTestCase): 
    131130
    132131    def test_integerfield_1(self):
    133132        f = IntegerField()
     133        self.assertWidgetRendersTo(f, '<input type="number" name="f" id="id_f" />')
    134134        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
    135135        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
    136136        self.assertEqual(1, f.clean('1'))
    class FieldsTests(SimpleTestCase): 
    165165
    166166    def test_integerfield_3(self):
    167167        f = IntegerField(max_value=10)
     168        self.assertWidgetRendersTo(f, '<input max="10" type="number" name="f" id="id_f" />')
    168169        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
    169170        self.assertEqual(1, f.clean(1))
    170171        self.assertEqual(10, f.clean(10))
    class FieldsTests(SimpleTestCase): 
    176177
    177178    def test_integerfield_4(self):
    178179        f = IntegerField(min_value=10)
     180        self.assertWidgetRendersTo(f, '<input id="id_f" type="number" name="f" min="10" />')
    179181        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
    180182        self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'", f.clean, 1)
    181183        self.assertEqual(10, f.clean(10))
    class FieldsTests(SimpleTestCase): 
    187189
    188190    def test_integerfield_5(self):
    189191        f = IntegerField(min_value=10, max_value=20)
     192        self.assertWidgetRendersTo(f, '<input id="id_f" max="20" type="number" name="f" min="10" />')
    190193        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
    191194        self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 10.'", f.clean, 1)
    192195        self.assertEqual(10, f.clean(10))
    class FieldsTests(SimpleTestCase): 
    198201        self.assertEqual(f.max_value, 20)
    199202        self.assertEqual(f.min_value, 10)
    200203
     204    def test_integerfield_localized(self):
     205        """
     206        Make sure localized IntegerField's widget renders to a text input with
     207        no number input specific attributes.
     208        """
     209        f1 = IntegerField(localize=True)
     210        self.assertWidgetRendersTo(f1, '<input id="id_f" name="f" type="text" />')
     211
    201212    # FloatField ##################################################################
    202213
    203214    def test_floatfield_1(self):
    204215        f = FloatField()
     216        self.assertWidgetRendersTo(f, '<input step="any" type="number" name="f" id="id_f" />')
    205217        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
    206218        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
    207219        self.assertEqual(1.0, f.clean('1'))
    class FieldsTests(SimpleTestCase): 
    228240
    229241    def test_floatfield_3(self):
    230242        f = FloatField(max_value=1.5, min_value=0.5)
     243        self.assertWidgetRendersTo(f, '<input step="any" name="f" min="0.5" max="1.5" type="number" id="id_f" />')
    231244        self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'", f.clean, '1.6')
    232245        self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'", f.clean, '0.4')
    233246        self.assertEqual(1.5, f.clean('1.5'))
    class FieldsTests(SimpleTestCase): 
    235248        self.assertEqual(f.max_value, 1.5)
    236249        self.assertEqual(f.min_value, 0.5)
    237250
     251    def test_floatfield_localized(self):
     252        """
     253        Make sure localized FloatField's widget renders to a text input with
     254        no number input specific attributes.
     255        """
     256        f = FloatField(localize=True)
     257        self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />')
     258
    238259    # DecimalField ################################################################
    239260
    240261    def test_decimalfield_1(self):
    241262        f = DecimalField(max_digits=4, decimal_places=2)
     263        self.assertWidgetRendersTo(f, '<input id="id_f" step="0.01" type="number" name="f" maxlength="6" />')
    242264        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, '')
    243265        self.assertRaisesMessage(ValidationError, "'This field is required.'", f.clean, None)
    244266        self.assertEqual(f.clean('1'), Decimal("1"))
    class FieldsTests(SimpleTestCase): 
    284306
    285307    def test_decimalfield_3(self):
    286308        f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
     309        self.assertWidgetRendersTo(f, '<input step="0.01" name="f" min="0.5" max="1.5" maxlength="6" type="number" id="id_f" />')
    287310        self.assertRaisesMessage(ValidationError, "'Ensure this value is less than or equal to 1.5.'", f.clean, '1.6')
    288311        self.assertRaisesMessage(ValidationError, "'Ensure this value is greater than or equal to 0.5.'", f.clean, '0.4')
    289312        self.assertEqual(f.clean('1.5'), Decimal("1.5"))
    class FieldsTests(SimpleTestCase): 
    315338        self.assertEqual(f.clean('.01'), Decimal(".01"))
    316339        self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 0 digits before the decimal point.'", f.clean, '1.1')
    317340
     341    def test_decimalfield_localized(self):
     342        """
     343        Make sure localized DecimalField's widget renders to a text input with
     344        no number input specific attributes.
     345        """
     346        f = DecimalField(localize=True)
     347        self.assertWidgetRendersTo(f, '<input id="id_f" name="f" type="text" />')
     348
    318349    # DateField ###################################################################
    319350
    320351    def test_datefield_1(self):
  • tests/regressiontests/forms/tests/forms.py

    diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py
    index f2fa78e..f856e30 100644
    a b class FormsTestCase(TestCase): 
    17401740<option value="3">No</option>
    17411741</select></li>
    17421742<li><label for="id_email">Email:</label> <input type="email" name="email" id="id_email" /></li>
    1743 <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>""")
     1743<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>""")
    17441744
    17451745        self.assertHTMLEqual(p.as_p(), """<ul class="errorlist"><li>This field is required.</li></ul>
    17461746<p class="required error"><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
    class FormsTestCase(TestCase): 
    17511751</select></p>
    17521752<p><label for="id_email">Email:</label> <input type="email" name="email" id="id_email" /></p>
    17531753<ul class="errorlist"><li>This field is required.</li></ul>
    1754 <p class="required error"><label for="id_age">Age:</label> <input type="text" name="age" id="id_age" /></p>""")
     1754<p class="required error"><label for="id_age">Age:</label> <input type="number" name="age" id="id_age" /></p>""")
    17551755
    17561756        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>
    17571757<tr class="required"><th><label for="id_is_cool">Is cool:</label></th><td><select name="is_cool" id="id_is_cool">
    class FormsTestCase(TestCase): 
    17601760<option value="3">No</option>
    17611761</select></td></tr>
    17621762<tr><th><label for="id_email">Email:</label></th><td><input type="email" name="email" id="id_email" /></td></tr>
    1763 <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>""")
     1763<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>""")
    17641764
    17651765    def test_label_split_datetime_not_displayed(self):
    17661766        class EventForm(Form):
  • tests/regressiontests/forms/tests/formsets.py

    diff --git a/tests/regressiontests/forms/tests/formsets.py b/tests/regressiontests/forms/tests/formsets.py
    index ef6f40c..d08110f 100644
    a b class FormsFormsetTestCase(TestCase): 
    5353        formset = ChoiceFormSet(auto_id=False, prefix='choices')
    5454        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" />
    5555<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
    56 <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>""")
     56<tr><th>Votes:</th><td><input type="number" name="choices-0-votes" /></td></tr>""")
    5757
    5858        # On thing to note is that there needs to be a special value in the data. This
    5959        # value tells the FormSet how many forms were displayed so it can tell how
    class FormsFormsetTestCase(TestCase): 
    137137            form_output.append(form.as_ul())
    138138
    139139        self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    140 <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
     140<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
    141141<li>Choice: <input type="text" name="choices-1-choice" /></li>
    142 <li>Votes: <input type="text" name="choices-1-votes" /></li>""")
     142<li>Votes: <input type="number" name="choices-1-votes" /></li>""")
    143143
    144144        # Let's simulate what would happen if we submitted this form.
    145145
    class FormsFormsetTestCase(TestCase): 
    210210            form_output.append(form.as_ul())
    211211
    212212        self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
    213 <li>Votes: <input type="text" name="choices-0-votes" /></li>
     213<li>Votes: <input type="number" name="choices-0-votes" /></li>
    214214<li>Choice: <input type="text" name="choices-1-choice" /></li>
    215 <li>Votes: <input type="text" name="choices-1-votes" /></li>
     215<li>Votes: <input type="number" name="choices-1-votes" /></li>
    216216<li>Choice: <input type="text" name="choices-2-choice" /></li>
    217 <li>Votes: <input type="text" name="choices-2-votes" /></li>""")
     217<li>Votes: <input type="number" name="choices-2-votes" /></li>""")
    218218
    219219        # Since we displayed every form as blank, we will also accept them back as blank.
    220220        # This may seem a little strange, but later we will show how to require a minimum
    class FormsFormsetTestCase(TestCase): 
    301301            form_output.append(form.as_ul())
    302302
    303303        self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    304 <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
     304<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
    305305<li>Choice: <input type="text" name="choices-1-choice" /></li>
    306 <li>Votes: <input type="text" name="choices-1-votes" /></li>
     306<li>Votes: <input type="number" name="choices-1-votes" /></li>
    307307<li>Choice: <input type="text" name="choices-2-choice" /></li>
    308 <li>Votes: <input type="text" name="choices-2-votes" /></li>
     308<li>Votes: <input type="number" name="choices-2-votes" /></li>
    309309<li>Choice: <input type="text" name="choices-3-choice" /></li>
    310 <li>Votes: <input type="text" name="choices-3-votes" /></li>""")
     310<li>Votes: <input type="number" name="choices-3-votes" /></li>""")
    311311
    312312        # Make sure retrieving an empty form works, and it shows up in the form list
    313313
    314314        self.assertTrue(formset.empty_form.empty_permitted)
    315315        self.assertHTMLEqual(formset.empty_form.as_ul(), """<li>Choice: <input type="text" name="choices-__prefix__-choice" /></li>
    316 <li>Votes: <input type="text" name="choices-__prefix__-votes" /></li>""")
     316<li>Votes: <input type="number" name="choices-__prefix__-votes" /></li>""")
    317317
    318318    def test_formset_with_deletion(self):
    319319        # FormSets with deletion ######################################################
    class FormsFormsetTestCase(TestCase): 
    331331            form_output.append(form.as_ul())
    332332
    333333        self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    334 <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
     334<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
    335335<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
    336336<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
    337 <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
     337<li>Votes: <input type="number" name="choices-1-votes" value="900" /></li>
    338338<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
    339339<li>Choice: <input type="text" name="choices-2-choice" /></li>
    340 <li>Votes: <input type="text" name="choices-2-votes" /></li>
     340<li>Votes: <input type="number" name="choices-2-votes" /></li>
    341341<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>""")
    342342
    343343        # To delete something, we just need to set that form's special delete field to
    class FormsFormsetTestCase(TestCase): 
    428428            form_output.append(form.as_ul())
    429429
    430430        self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    431 <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
    432 <li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
     431<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
     432<li>Order: <input type="number" name="choices-0-ORDER" value="1" /></li>
    433433<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
    434 <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
    435 <li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
     434<li>Votes: <input type="number" name="choices-1-votes" value="900" /></li>
     435<li>Order: <input type="number" name="choices-1-ORDER" value="2" /></li>
    436436<li>Choice: <input type="text" name="choices-2-choice" /></li>
    437 <li>Votes: <input type="text" name="choices-2-votes" /></li>
    438 <li>Order: <input type="text" name="choices-2-ORDER" /></li>""")
     437<li>Votes: <input type="number" name="choices-2-votes" /></li>
     438<li>Order: <input type="number" name="choices-2-ORDER" /></li>""")
    439439
    440440        data = {
    441441            'choices-TOTAL_FORMS': '3', # the number of forms rendered
    class FormsFormsetTestCase(TestCase): 
    539539            form_output.append(form.as_ul())
    540540
    541541        self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    542 <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>
    543 <li>Order: <input type="text" name="choices-0-ORDER" value="1" /></li>
     542<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>
     543<li>Order: <input type="number" name="choices-0-ORDER" value="1" /></li>
    544544<li>Delete: <input type="checkbox" name="choices-0-DELETE" /></li>
    545545<li>Choice: <input type="text" name="choices-1-choice" value="Fergie" /></li>
    546 <li>Votes: <input type="text" name="choices-1-votes" value="900" /></li>
    547 <li>Order: <input type="text" name="choices-1-ORDER" value="2" /></li>
     546<li>Votes: <input type="number" name="choices-1-votes" value="900" /></li>
     547<li>Order: <input type="number" name="choices-1-ORDER" value="2" /></li>
    548548<li>Delete: <input type="checkbox" name="choices-1-DELETE" /></li>
    549549<li>Choice: <input type="text" name="choices-2-choice" value="The Decemberists" /></li>
    550 <li>Votes: <input type="text" name="choices-2-votes" value="500" /></li>
    551 <li>Order: <input type="text" name="choices-2-ORDER" value="3" /></li>
     550<li>Votes: <input type="number" name="choices-2-votes" value="500" /></li>
     551<li>Order: <input type="number" name="choices-2-ORDER" value="3" /></li>
    552552<li>Delete: <input type="checkbox" name="choices-2-DELETE" /></li>
    553553<li>Choice: <input type="text" name="choices-3-choice" /></li>
    554 <li>Votes: <input type="text" name="choices-3-votes" /></li>
    555 <li>Order: <input type="text" name="choices-3-ORDER" /></li>
     554<li>Votes: <input type="number" name="choices-3-votes" /></li>
     555<li>Order: <input type="number" name="choices-3-ORDER" /></li>
    556556<li>Delete: <input type="checkbox" name="choices-3-DELETE" /></li>""")
    557557
    558558        # Let's delete Fergie, and put The Decemberists ahead of Calexico.
    class FormsetAsFooTests(TestCase): 
    898898        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    899899        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" />
    900900<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
    901 <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" value="100" /></td></tr>""")
     901<tr><th>Votes:</th><td><input type="number" name="choices-0-votes" value="100" /></td></tr>""")
    902902
    903903    def test_as_p(self):
    904904        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    905905        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" />
    906906<p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
    907 <p>Votes: <input type="text" name="choices-0-votes" value="100" /></p>""")
     907<p>Votes: <input type="number" name="choices-0-votes" value="100" /></p>""")
    908908
    909909    def test_as_ul(self):
    910910        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    911911        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" />
    912912<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    913 <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
     913<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>""")
    914914
    915915
    916916# Regression test for #11418 #################################################
  • tests/regressiontests/i18n/tests.py

    diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
    index 7b4fd0e..ca34007 100644
    a b class FormattingTests(TestCase): 
    662662        """
    663663        Tests if form input is correctly localized
    664664        """
     665        self.maxDiff = 1200
    665666        settings.USE_L10N = True
    666667        with translation.override('de-at', deactivate=True):
    667668            form6 = CompanyForm({
Back to Top