From 4d0c5f61427a8e67552ee2d777fffbadc7aff3b2 Mon Sep 17 00:00:00 2001 From: Dejan Noveski Date: Thu, 13 Mar 2014 12:23:12 +0100 Subject: [PATCH] Fixed #22255 -- Added support for specifying re flags in RegexValidator --- django/core/validators.py | 9 +++++++-- docs/ref/validators.txt | 14 +++++++++++++- docs/releases/1.7.txt | 19 +++++++++++++++---- tests/forms_tests/tests/test_validators.py | 15 ++++++++++++++- tests/validators/tests.py | 12 ++++++++++++ 5 files changed, 61 insertions(+), 8 deletions(-) diff --git a/django/core/validators.py b/django/core/validators.py index 83e1a12a2f..4b7627cc7c 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -21,8 +21,9 @@ class RegexValidator(object): message = _('Enter a valid value.') code = 'invalid' 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: self.regex = regex if message is not None: @@ -31,10 +32,14 @@ class RegexValidator(object): self.code = code if inverse_match is not None: 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. 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): """ diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index b3ae3c6d41..eb2e1d0d02 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -59,13 +59,16 @@ to, or in lieu of custom ``field.clean()`` methods. ``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 expression string or a pre-compiled regular expression. :param message: If not ``None``, overrides :attr:`.message`. :param code: If not ``None``, overrides :attr:`code`. :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 @@ -93,6 +96,15 @@ to, or in lieu of custom ``field.clean()`` methods. 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`` ---------------- .. class:: URLValidator([schemes=None, regex=None, message=None, code=None]) diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 934ad017c9..9df1b666d5 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -795,11 +795,15 @@ Tests Validators ^^^^^^^^^^ -* :class:`~django.core.validators.RegexValidator` now accepts an optional - Boolean :attr:`~django.core.validators.RegexValidator.inverse_match` argument - which determines if the :exc:`~django.core.exceptions.ValidationError` should +* :class:`~django.core.validators.RegexValidator` now accepts the optional + :attr:`~django.core.validators.RegexValidator.flags` and + 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 - 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 ``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, 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: Features deprecated in 1.7 diff --git a/tests/forms_tests/tests/test_validators.py b/tests/forms_tests/tests/test_validators.py index bd098d150a..c69597b497 100644 --- a/tests/forms_tests/tests/test_validators.py +++ b/tests/forms_tests/tests/test_validators.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import re from unittest import TestCase 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): 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') try: @@ -38,3 +50,4 @@ class TestFieldWithValidators(TestCase): self.assertFalse(form.is_valid()) self.assertEqual(form.errors['string'], ["Letters only."]) + self.assertEqual(form.errors['string'], ["Letters only."]) diff --git a/tests/validators/tests.py b/tests/validators/tests.py index b88d71fa68..25f1e1b2f2 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -198,6 +198,10 @@ TEST_DATA = ( (RegexValidator(re.compile('x'), inverse_match=True), 'y', None), (RegexValidator('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(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 for validator, value, expected in TEST_DATA: name, method = create_simple_test_method(validator, expected, value, test_counter)