Repository: django-extensions/django-extensions

Issue:
HexValidator bug in max_length ?
Hello,
I'm seeing unexpected validation errors when using max_length with HexValidator.
Django-extensions version is 3.1.1. Django is 3.1.7.

`myfield = models.CharField(max_length=20, unique=True, validators=[HexValidator(max_length=12)])`

django.core.exceptions.ValidationError: {'myfield': ['Ensure that there are no more than 12 characters.']}
The above exception was triggered when validating a 10-digits string ('0123456789') as if min_length and max_length were confused somewhere.

Best regards


Discussion:

Resolving patch:
diff --git a/django_extensions/validators.py b/django_extensions/validators.py
--- a/django_extensions/validators.py
+++ b/django_extensions/validators.py
@@ -83,6 +83,8 @@ def __init__(self, length=None, min_length=None, max_length=None, message=None,
         self.max_length = max_length
         if message:
             self.message = message
+        else:
+            self.message = self.messages['invalid']
         if code:
             self.code = code
 
@@ -92,7 +94,7 @@ def __call__(self, value):
             raise ValidationError(self.messages['length'], code='hex_only_length', params={'length': self.length})
         if self.min_length and len(value) < self.min_length:
             raise ValidationError(self.messages['min_length'], code='hex_only_min_length', params={'min': self.min_length})
-        if self.max_length and len(value) < self.max_length:
+        if self.max_length and len(value) > self.max_length:
             raise ValidationError(self.messages['max_length'], code='hex_only_max_length', params={'max': self.max_length})
 
         try:


Test patch:
diff --git a/tests/test_validators.py b/tests/test_validators.py
--- a/tests/test_validators.py
+++ b/tests/test_validators.py
@@ -1,8 +1,10 @@
 # -*- coding: utf-8 -*-
+from unittest.mock import patch
+
 from django.core.exceptions import ValidationError
-from django.test import TestCase
+from django.test import TestCase, SimpleTestCase
 
-from django_extensions.validators import NoControlCharactersValidator, NoWhitespaceValidator
+from django_extensions.validators import NoControlCharactersValidator, NoWhitespaceValidator, HexValidator
 
 
 class NoControlCharactersValidatorTests(TestCase):
@@ -72,3 +74,82 @@ def test_should_not_raise_if_value_doesnt_have_leading_or_trailing_whitespaces(s
         result = self.validator(value_without_leading_or_trailing_whitespaces)
 
         self.assertIsNone(result)
+
+
+class TestHexValidator(SimpleTestCase):
+    def test_custom_message_and_code(self):
+        self.validator = HexValidator(message='message', code='code')
+
+        self.assertEqual(self.validator.message, 'message')
+        self.assertEqual(self.validator.code, 'code')
+
+    def test_equality_of_objs_with_obj_of_different_type(self):
+        self.assertNotEqual(TypeError(), HexValidator())
+
+    def test_equality_of_objs_with_different_code(self):
+        self.assertNotEqual(HexValidator(code='1'), HexValidator(code='a'))
+
+    def test_equality_of_objs_with_different_message(self):
+        self.assertNotEqual(HexValidator(code='code', message='a'), HexValidator(code='code', message='acb'))
+
+    def test_equality_of_objs_with_same_code_and_message(self):
+        self.assertEqual(HexValidator(code='c', message='m'), HexValidator(code='c', message='m'))
+
+    def test_fixed_length(self):
+        value = 'abcd'
+        self.validator = HexValidator(length=5)
+
+        with self.assertRaises(ValidationError) as err:
+            self.validator(value)
+
+        self.assertEqual(str(err.exception), "['Invalid length. Must be 5 characters.']")
+        self.assertEqual(err.exception.code, 'hex_only_length')
+
+    def test_min_length(self):
+        value = 'a'
+        self.validator = HexValidator(min_length=5)
+
+        with self.assertRaises(ValidationError) as err:
+            self.validator(value)
+
+        self.assertEqual(str(err.exception), "['Ensure that there are more than 5 characters.']")
+        self.assertEqual(err.exception.code, 'hex_only_min_length')
+
+    def test_with_max_length(self):
+        value = 'abcd'
+        self.validator = HexValidator(max_length=2)
+
+        with self.assertRaises(ValidationError) as err:
+            self.validator(value)
+
+        self.assertEqual(str(err.exception), "['Ensure that there are no more than 2 characters.']")
+        self.assertEqual(err.exception.code, 'hex_only_max_length')
+
+    def test_invalid_type(self):
+        value = 1
+        with patch('django_extensions.validators.force_str', return_value=1):
+            self.validator = HexValidator()
+
+            with self.assertRaises(ValidationError) as err:
+                self.validator(value)
+
+        self.assertEqual(str(err.exception), "['Only a hex string is allowed.']")
+        self.assertEqual(err.exception.code, 'hex_only')
+
+    def test_invalid_hex(self):
+        value = '1'
+        self.validator = HexValidator()
+
+        with self.assertRaises(ValidationError) as err:
+            self.validator(value)
+
+        self.assertEqual(str(err.exception), "['Only a hex string is allowed.']")
+        self.assertEqual(err.exception.code, 'hex_only')
+
+    def test_valid_hex(self):
+        value = 'b901ef'
+        self.validator = HexValidator()
+
+        result = self.validator(value)
+
+        self.assertIsNone(result)