Fixed #22255 -- Added support for specifying re flags in RegexValidator

This commit is contained in:
Dejan Noveski 2014-03-13 12:23:12 +01:00 committed by Erik Romijn
parent f2eea960e0
commit 4d0c5f6142
5 changed files with 61 additions and 8 deletions

View File

@ -21,8 +21,9 @@ class RegexValidator(object):
message = _('Enter a valid value.') message = _('Enter a valid value.')
code = 'invalid' code = 'invalid'
inverse_match = False inverse_match = False
flags = 0
def __init__(self, regex=None, message=None, code=None, inverse_match=None): def __init__(self, regex=None, message=None, code=None, inverse_match=None, flags=None):
if regex is not None: if regex is not None:
self.regex = regex self.regex = regex
if message is not None: if message is not None:
@ -31,10 +32,14 @@ class RegexValidator(object):
self.code = code self.code = code
if inverse_match is not None: if inverse_match is not None:
self.inverse_match = inverse_match self.inverse_match = inverse_match
if flags is not None:
self.flags = flags
if self.flags and not isinstance(self.regex, six.string_types):
raise TypeError("If the flags are set, regex must be a regular expression string.")
# Compile the regex if it was not passed pre-compiled. # Compile the regex if it was not passed pre-compiled.
if isinstance(self.regex, six.string_types): if isinstance(self.regex, six.string_types):
self.regex = re.compile(self.regex) self.regex = re.compile(self.regex, self.flags)
def __call__(self, value): def __call__(self, value):
""" """

View File

@ -59,13 +59,16 @@ to, or in lieu of custom ``field.clean()`` methods.
``RegexValidator`` ``RegexValidator``
------------------ ------------------
.. class:: RegexValidator([regex=None, message=None, code=None, inverse_match=None]) .. class:: RegexValidator([regex=None, message=None, code=None, inverse_match=None, flags=0])
:param regex: If not ``None``, overrides :attr:`regex`. Can be a regular :param regex: If not ``None``, overrides :attr:`regex`. Can be a regular
expression string or a pre-compiled regular expression. expression string or a pre-compiled regular expression.
:param message: If not ``None``, overrides :attr:`.message`. :param message: If not ``None``, overrides :attr:`.message`.
:param code: If not ``None``, overrides :attr:`code`. :param code: If not ``None``, overrides :attr:`code`.
:param inverse_match: If not ``None``, overrides :attr:`inverse_match`. :param inverse_match: If not ``None``, overrides :attr:`inverse_match`.
:param flags: If not ``None``, overrides :attr:`flags`. In that case,
:attr:`regex` must be a regular expression string, or
:exc:`~exceptions.TypeError` is raised.
.. attribute:: regex .. attribute:: regex
@ -93,6 +96,15 @@ to, or in lieu of custom ``field.clean()`` methods.
The match mode for :attr:`regex`. Defaults to ``False``. The match mode for :attr:`regex`. Defaults to ``False``.
.. attribute:: flags
.. versionadded:: 1.7
The flags used when compiling the regular expression string :attr:`regex`.
If :attr:`regex` is a pre-compiled regular expression, and :attr:`flags` is overridden,
:exc:`~exceptions.TypeError` is raised.
Defaults to `0`.
``URLValidator`` ``URLValidator``
---------------- ----------------
.. class:: URLValidator([schemes=None, regex=None, message=None, code=None]) .. class:: URLValidator([schemes=None, regex=None, message=None, code=None])

View File

@ -795,11 +795,15 @@ Tests
Validators Validators
^^^^^^^^^^ ^^^^^^^^^^
* :class:`~django.core.validators.RegexValidator` now accepts an optional * :class:`~django.core.validators.RegexValidator` now accepts the optional
Boolean :attr:`~django.core.validators.RegexValidator.inverse_match` argument :attr:`~django.core.validators.RegexValidator.flags` and
which determines if the :exc:`~django.core.exceptions.ValidationError` should Boolean :attr:`~django.core.validators.RegexValidator.inverse_match` arguments.
The :attr:`~django.core.validators.RegexValidator.inverse_match` attribute
determines if the :exc:`~django.core.exceptions.ValidationError` should
be raised when the regular expression pattern matches (``True``) or does not be raised when the regular expression pattern matches (``True``) or does not
match (``False``, by default) the provided ``value``. match (``False``, by default) the provided ``value``. The
:attr:`~django.core.validators.RegexValidator.flags` attribute sets the flags
used when compiling a regular expression string.
* :class:`~django.core.validators.URLValidator` now accepts an optional * :class:`~django.core.validators.URLValidator` now accepts an optional
``schemes`` argument which allows customization of the accepted URI schemes ``schemes`` argument which allows customization of the accepted URI schemes
@ -1191,6 +1195,13 @@ Miscellaneous
a relation from the related object back to the content type for filtering, a relation from the related object back to the content type for filtering,
ordering and other query operations. ordering and other query operations.
* When a model field's :attr:`~django.db.models.Field.validators` contains
a :class:`~django.core.validators.RegexValidator`, the regular expression
must now be passed as a regular expression string. You can no longer use a
pre-compiled regular expression in this case, as it is not serializable.
The :attr:`~django.core.validators.RegexValidator.flags` attribute was added
to :class:`~django.core.validators.RegexValidator` to simplify this change.
.. _deprecated-features-1.7: .. _deprecated-features-1.7:
Features deprecated in 1.7 Features deprecated in 1.7

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re
from unittest import TestCase from unittest import TestCase
from django import forms from django import forms
@ -24,11 +25,22 @@ class UserForm(forms.Form):
) )
] ]
) )
ignore_case_string = forms.CharField(
max_length=50,
validators=[
validators.RegexValidator(
regex='^[a-z]*$',
message="Letters only.",
flags=re.IGNORECASE,
)
]
)
class TestFieldWithValidators(TestCase): class TestFieldWithValidators(TestCase):
def test_all_errors_get_reported(self): def test_all_errors_get_reported(self):
form = UserForm({'full_name': 'not int nor mail', 'string': '2 is not correct'}) form = UserForm({'full_name': 'not int nor mail', 'string': '2 is not correct', 'ignore_case_string': "IgnORE Case strIng"})
self.assertRaises(ValidationError, form.fields['full_name'].clean, 'not int nor mail') self.assertRaises(ValidationError, form.fields['full_name'].clean, 'not int nor mail')
try: try:
@ -38,3 +50,4 @@ class TestFieldWithValidators(TestCase):
self.assertFalse(form.is_valid()) self.assertFalse(form.is_valid())
self.assertEqual(form.errors['string'], ["Letters only."]) self.assertEqual(form.errors['string'], ["Letters only."])
self.assertEqual(form.errors['string'], ["Letters only."])

View File

@ -198,6 +198,10 @@ TEST_DATA = (
(RegexValidator(re.compile('x'), inverse_match=True), 'y', None), (RegexValidator(re.compile('x'), inverse_match=True), 'y', None),
(RegexValidator('x', inverse_match=True), 'x', ValidationError), (RegexValidator('x', inverse_match=True), 'x', ValidationError),
(RegexValidator(re.compile('x'), inverse_match=True), 'x', ValidationError), (RegexValidator(re.compile('x'), inverse_match=True), 'x', ValidationError),
(RegexValidator('x', flags=re.IGNORECASE), 'y', ValidationError),
(RegexValidator('a'), 'A', ValidationError),
(RegexValidator('a', flags=re.IGNORECASE), 'A', None),
) )
@ -250,6 +254,14 @@ class TestSimpleValidators(TestCase):
self.assertEqual(str(v), str_prefix("{%(_)s'first': [%(_)s'First Problem']}")) self.assertEqual(str(v), str_prefix("{%(_)s'first': [%(_)s'First Problem']}"))
self.assertEqual(repr(v), str_prefix("ValidationError({%(_)s'first': [%(_)s'First Problem']})")) self.assertEqual(repr(v), str_prefix("ValidationError({%(_)s'first': [%(_)s'First Problem']})"))
def test_regex_validator_flags(self):
try:
RegexValidator(re.compile('a'), flags=re.IGNORECASE)
except TypeError:
pass
else:
self.fail("TypeError not raised when flags and pre-compiled regex in RegexValidator")
test_counter = 0 test_counter = 0
for validator, value, expected in TEST_DATA: for validator, value, expected in TEST_DATA:
name, method = create_simple_test_method(validator, expected, value, test_counter) name, method = create_simple_test_method(validator, expected, value, test_counter)