Commits

Anonymous committed 61065d4

Add beginnings of translation hook support. Refs #32.

Thanks Christopher Grebs(EnTeQuAk) for the initial translations support, and
Rodrigo Moraes for additional integration on validators.

  • Participants
  • Parent commits 7d74734

Comments (0)

Files changed (5)

 
 - Ali Aafshar
 - Adam Lowry
+- Christopher Grebs
 - Emil Vladev
 - Rodrigo Moraes
 - Sebastian Wiesner

tests/validators.py

 from unittest import TestCase
 from wtforms.validators import StopValidation, ValidationError, email, equal_to, ip_address, length, required, optional, regexp, url, NumberRange, AnyOf, NoneOf
 
+class DummyTranslations(object):
+    def gettext(self, string):
+        return string
+
+    def ngettext(self, singular, plural, n):
+        if n == 1:
+            return singular
+
+        return plural
+
+_dummy_translations = DummyTranslations()
 
 class DummyForm(dict):
-    pass
+    def _get_translations(self):
+        """Has to return a gettext translations object that supports
+        unicode. By default a dummy is returned.
+        """
+        return _dummy_translations
 
 class DummyField(object):
+    _translations = _dummy_translations
     def __init__(self, data, errors=(), raw_data=None):
         self.data = data
         self.errors = list(errors)
         self.raw_data = raw_data
 
+    def gettext(self, string):
+        return self._translations.gettext(string)
+
+    def ngettext(self, singular, plural, n):
+        return self._translations.ngettext(singular, plural, n)
+
 def grab_error_message(callable, form, field):
     try:
         callable(form, field)

wtforms/fields.py

 _unset_value = object()
 
 
+class DummyTranslations(object):
+    def gettext(self, string):
+        return string
+
+    def ngettext(self, singular, plural, n):
+        if n == 1:
+            return singular
+
+        return plural
+
+
 class Field(object):
     """
     Field base class
     errors = tuple()
     process_errors = tuple()
     _formfield = True
+    _translations = DummyTranslations()
 
     def __new__(cls, *args, **kwargs):
         if '_form' in kwargs and '_name' in kwargs:
 
     def __init__(self, label=u'', validators=None, filters=tuple(),
                  description=u'', id=None, default=None, widget=None,
-                 _form=None, _name=None, _prefix=''):
+                 _form=None, _name=None, _prefix='', _translations=None):
         """
         Construct a new field.
 
         """
         self.short_name = _name
         self.name = _prefix + _name
+        if _translations is not None:
+            self._translations = _translations
         self.id = id or self.name
         self.label = Label(self.id, label or _name.replace('_', ' ').title())
         if validators is None:
         """
         return self.widget(self, **kwargs)
 
+    def gettext(self, string):
+        return self._translations.gettext(string)
+
+    def ngettext(self, singular, plural, n):
+        return self._translations.ngettext(singular, plural, n)
+
     def validate(self, form, extra_validators=tuple()):
         """
         Validates the field and returns True or False. `self.errors` will
         self.kwargs = kwargs
         self.creation_counter = UnboundField.creation_counter
 
-    def bind(self, form, name, prefix='', **kwargs):
-        return self.field_class(_form=form, _prefix=prefix, _name=name, *self.args, **dict(self.kwargs, **kwargs))
+    def bind(self, form, name, prefix='', translations=None, **kwargs):
+        return self.field_class(_form=form, _prefix=prefix, _name=name, _translations=translations, *self.args, **dict(self.kwargs, **kwargs))
 
     def __repr__(self):
         return '<UnboundField(%s, %r, %r)>' % (self.field_class.__name__, self.args, self.kwargs)
             try:
                 self.data = self.coerce(valuelist[0])
             except ValueError:
-                raise ValueError(u'Invalid Choice: could not coerce')
+                raise ValueError(self.gettext(u'Invalid Choice: could not coerce'))
 
     def pre_validate(self, form):
         for v, _ in self.choices:
             if self.data == v:
                 break
         else:
-            raise ValueError('Not a valid choice')
+            raise ValueError(self.gettext(u'Not a valid choice'))
 
 
 class SelectMultipleField(SelectField):
         try:
             self.data = list(self.coerce(x) for x in valuelist)
         except ValueError:
-            raise ValueError(u'Invalid choice(s): one or more data inputs could not be coerced')
+            raise ValueError(self.gettext(u'Invalid choice(s): one or more data inputs could not be coerced'))
 
     def pre_validate(self, form):
         if self.data:
             values = list(c[0] for c in self.choices)
             for d in self.data:
                 if d not in values:
-                    raise ValueError(u"'%s' is not a valid choice for this field" % d)
+                    raise ValueError(self.gettext(u"'%(value)s' is not a valid choice for this field") % dict(value=d))
 
 
 class RadioField(SelectField):
             try:
                 self.data = int(valuelist[0])
             except ValueError:
-                raise ValueError(u'Not a valid integer value')
+                raise ValueError(self.gettext(u'Not a valid integer value'))
 
 
 class DecimalField(TextField):
             try:
                 self.data = decimal.Decimal(valuelist[0])
             except (decimal.InvalidOperation, ValueError):
-                raise ValueError(u'Not a valid decimal value')
+                raise ValueError(self.gettext(u'Not a valid decimal value'))
 
 
 class FloatField(TextField):
             try:
                 self.data = float(valuelist[0])
             except ValueError:
-                raise ValueError(u'Not a valid float value')
+                raise ValueError(self.gettext(u'Not a valid float value'))
 
 
 class BooleanField(Field):
         if hasattr(fields, 'iteritems'):
             fields = fields.iteritems()
 
+        translations = self._get_translations()
+
         for name, unbound_field in fields:
-            field = unbound_field.bind(form=self, name=name, prefix=prefix)
+            field = unbound_field.bind(form=self, name=name, prefix=prefix, translations=translations)
             self._fields[name] = field
 
     def __iter__(self):
         """ Remove a field from this form. """
         del self._fields[name]
 
+    def _get_translations(self):
+        """
+        Override in subclasses to provide alternate translations factory.
+
+        Must return an object that provides gettext() and ngettext() methods.
+        """
+        return None
+
     def populate_obj(self, obj):
         """
         Populates the attributes of the passed `obj` with data from the form's

wtforms/validators.py

         interpolated with `%(other_label)s` and `%(other_name)s` to provide a
         more helpful error.
     """
-    def __init__(self, fieldname, message=u'Field must be equal to %(other_name)s'):
+    def __init__(self, fieldname, message=None):
         self.fieldname = fieldname
         self.message = message
 
         try:
             other = form[self.fieldname]
         except KeyError:
-            raise ValidationError(u"Invalid field name '%s'" % self.fieldname)
+            raise ValidationError(field.gettext(u"Invalid field name '%s'.") % self.fieldname)
         if field.data != other.data:
             d = {
                 'other_label': hasattr(other, 'label') and other.label.text or self.fieldname,
                 'other_name': self.fieldname
             }
+            if self.message is None:
+                self.message = field.gettext(u'Field must be equal to %(other_name)s.')
+
             raise ValidationError(self.message % d)
 
 
         assert max == -1 or min <= max, '`min` cannot be more than `max`.'
         self.min = min
         self.max = max
-
-        if message is None:
-            if max == -1:
-                message = u'Field must be at least %(min)d characters long.'
-            elif min == -1:
-                message = u'Field cannot be longer than %(max)d characters.'
-            else:
-                message = u'Field must be between %(min)d and %(max)d characters long.'
-
         self.message = message
 
     def __call__(self, form, field):
         l = field.data and len(field.data) or 0
         if l < self.min or self.max != -1 and l > self.max:
+            if self.message is None:
+                if self.max == -1:
+                    self.message = field.ngettext(u'Field must be at least %(min)d character long.',
+                                                  u'Field must be at least %(min)d characters long.', self.min)
+                elif self.min == -1:
+                    self.message = field.ngettext(u'Field cannot be longer than %(max)d character.',
+                                                  u'Field cannot be longer than %(max)d characters.', self.max)
+                else:
+                    self.message = field.gettext(u'Field must be between %(min)d and %(max)d characters long.')
+
             raise ValidationError(self.message % dict(min=self.min, max=self.max))
 
 
     def __init__(self, min=None, max=None, message=None):
         self.min = min
         self.max = max
-        if message is None:
-            # we use %(min)s interpolation to support floats, None, and
-            # Decimals without throwing a formatting exception.
-            if max is None:
-                message = u'Number must be greater than %(min)s'
-            elif min is None:
-                message = u'Number must be less than %(max)s'
-            else:
-                message = u'Number must be between %(min)s and %(max)s'
         self.message = message
 
     def __call__(self, form, field):
         data = field.data
         if data is None or (self.min is not None and data < self.min) or \
             (self.max is not None and data > self.max):
+            if self.message is None:
+                # we use %(min)s interpolation to support floats, None, and
+                # Decimals without throwing a formatting exception.
+                if self.max is None:
+                    self.message = field.gettext(u'Number must be greater than %(min)s.')
+                elif self.min is None:
+                    self.message = field.gettext(u'Number must be less than %(max)s.')
+                else:
+                    self.message = field.gettext(u'Number must be between %(min)s and %(max)s.')
+
             raise ValidationError(self.message % dict(min=self.min, max=self.max))
 
 
     """
     field_flags = ('required', )
 
-    def __init__(self, message=u'This field is required.'):
+    def __init__(self, message=None):
         self.message = message
 
     def __call__(self, form, field):
         if not field.data or isinstance(field.data, basestring) and not field.data.strip():
+            if self.message is None:
+                self.message = field.gettext(u'This field is required.')
+
             field.errors[:] = []
             raise StopValidation(self.message)
 
         expression pattern.
     :param flags:
         The regexp flags to use, for example re.IGNORECASE. Ignored if
-        `regex` is not a string.  
+        `regex` is not a string.
     :param message:
         Error message to raise in case of a validation error.
     """
-    def __init__(self, regex, flags=0, message=u'Invalid input.'):
+    def __init__(self, regex, flags=0, message=None):
         if isinstance(regex, basestring):
             regex = re.compile(regex, flags)
-        self.regex = regex 
+        self.regex = regex
         self.message = message
 
     def __call__(self, form, field):
         if not self.regex.match(field.data or u''):
+            if self.message is None:
+                self.message = field.gettext(u'Invalid input.')
+
             raise ValidationError(self.message)
 
 
     :param message:
         Error message to raise in case of a validation error.
     """
-    def __init__(self, message=u'Invalid email address.'):
+    def __init__(self, message=None):
         super(Email, self).__init__(r'^.+@[^.].*\.[a-z]{2,10}$', re.IGNORECASE, message)
 
+    def __call__(self, form, field):
+        if self.message is None:
+            self.message = field.gettext(u'Invalid email address.')
+
+        super(Email, self).__call__(form, field)
+
 
 class IPAddress(Regexp):
     """
     :param message:
         Error message to raise in case of a validation error.
     """
-    def __init__(self, message=u'Invalid IP address.'):
+    def __init__(self, message=None):
         super(IPAddress, self).__init__(r'^([0-9]{1,3}\.){3}[0-9]{1,3}$', message=message)
 
+    def __call__(self, form, field):
+        if self.message is None:
+            self.message = field.gettext(u'Invalid IP address.')
+
+        super(IPAddress, self).__call__(form, field)
+
 
 class URL(Regexp):
     """
     Simple regexp based url validation. Much like the email validator, you
-    probably want to validate the url later by other means if the url must 
+    probably want to validate the url later by other means if the url must
     resolve.
 
     :param require_tld:
         If true, then the domain-name portion of the URL must contain a .tld
-        suffix.  Set this to false if you want to allow domains like 
+        suffix.  Set this to false if you want to allow domains like
         `localhost`.
     :param message:
         Error message to raise in case of a validation error.
     """
-    def __init__(self, require_tld=True, message=u'Invalid URL.'):
+    def __init__(self, require_tld=True, message=None):
         tld_part = (require_tld and ur'\.[a-z]{2,10}' or u'')
         regex = ur'^[a-z]+://([^/:]+%s|([0-9]{1,3}\.){3}[0-9]{1,3})(:[0-9]+)?(\/.*)?$' % tld_part
         super(URL, self).__init__(regex, re.IGNORECASE, message)
 
+    def __call__(self, form, field):
+        if self.message is None:
+            self.message = field.gettext(u'Invalid URL.')
+
+        super(URL, self).__call__(form, field)
+
 
 class AnyOf(object):
     """
     :param values_formatter:
         Function used to format the list of values in the error message.
     """
-    def __init__(self, values, message=u'Invalid value, must be one of: %(values)s',
-                 values_formatter=None):
+    def __init__(self, values, message=None, values_formatter=None):
         self.values = values
         self.message = message
         if values_formatter is None:
 
     def __call__(self, form, field):
         if field.data not in self.values:
+            if self.message is None:
+                self.message = field.gettext(u'Invalid value, must be one of: %(values)s.')
+
             raise ValueError(self.message % dict(values=self.values_formatter(self.values)))
 
 
     :param values_formatter:
         Function used to format the list of values in the error message.
     """
-    def __init__(self, values, message=u'Invalid value, can\'t be any of: %(values)s',
-                 values_formatter=None):
+    def __init__(self, values, message=None, values_formatter=None):
         self.values = values
         self.message = message
         if values_formatter is None:
 
     def __call__(self, form, field):
         if field.data in self.values:
+            if self.message is None:
+                self.message = field.gettext(u'Invalid value, can\'t be any of: %(values)s.')
+
             raise ValueError(self.message % dict(values=self.values_formatter(self.values)))