Commit 7ec2a21b authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #19686 -- Added HTML5 number input type

Thanks Simon Charette for his help on the patch. Refs #16630.
parent dcf651c2
Loading
Loading
Loading
Loading
+34 −11
Original line number Diff line number Diff line
@@ -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):
+5 −1
Original line number Diff line number Diff line
@@ -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'

+6 −3
Original line number Diff line number Diff line
@@ -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
+13 −0
Original line number Diff line number Diff line
@@ -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``
~~~~~~~~~~~~~~

+9 −4
Original line number Diff line number Diff line
@@ -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
Loading