Skip to content

Commit

Permalink
add regex validation
Browse files Browse the repository at this point in the history
  • Loading branch information
saxix committed Jul 2, 2024
1 parent a683533 commit 06afb89
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 8 deletions.
9 changes: 9 additions & 0 deletions src/hope_flex_fields/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from .fields import FlexField
from .registry import field_registry
from .validators import JsValidator, ReValidator

if TYPE_CHECKING:
from .forms import FieldsetForm
Expand Down Expand Up @@ -88,6 +89,13 @@ def required(self):
def get_field(self) -> "FlexField":
try:
kwargs = dict(self.attrs)
validators = []
if self.validation:
validators.append(JsValidator(self.validation))
if self.regex:
validators.append(ReValidator(self.regex))

kwargs["validators"] = validators
field_class = type(f"{self.name}Field", (FlexField, self.field_type), {})
fld = field_class(**kwargs)
except Exception as e: # pragma: no cover
Expand Down Expand Up @@ -129,6 +137,7 @@ def validate(self, data):
if form.is_valid():
return True
else:
self.errors = form.errors
raise ValidationError(form.errors)


Expand Down
21 changes: 19 additions & 2 deletions src/hope_flex_fields/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,29 @@

from django.core.exceptions import ValidationError
from django.core.validators import BaseValidator
from django.utils.functional import cached_property
from django.utils.translation import gettext as _

from py_mini_racer import JSArray, JSObject, MiniRacer


class ReValidator(BaseValidator):
@cached_property
def rex(self):
return self.limit_value

def __call__(self, value):
try:
m = self.rex.match(str(value))
if not m:
raise ValueError()
except ValueError:
raise ValidationError(
"Invalid format. Allowed Regex is '%s'" % self.rex.pattern
)
return True


class JsValidator(BaseValidator):

@property
Expand All @@ -15,7 +33,6 @@ def code(self):

def __call__(self, value):
ctx = MiniRacer()

pickled = json.dumps(value or "")
base = f"var value = {pickled};"
ctx.eval(base)
Expand All @@ -28,7 +45,7 @@ def __call__(self, value):
errors = {s: k for s, k in ret.items()}
raise ValidationError(errors)

if isinstance(ret, str):
if isinstance(ret, str) and ret.strip() != "":
raise ValidationError(_(ret))
elif isinstance(ret, bool) and not ret:
raise ValidationError(_("Please insert a valid value"))
Expand Down
2 changes: 1 addition & 1 deletion tests/admin/test_admin_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def test_fields_default_attrs_if_error(app, record):
res.form["name"] = "Int"
res.form["field_type"] = fqn(forms.IntegerField)
res.form["attrs"] = "{aaa}"
res = res.form.submit("_continue").follow()
res.form.submit("_continue").follow()
obj: FieldDefinition = FieldDefinition.objects.get(name="int")
assert obj.attrs == {
"max_value": None,
Expand Down
11 changes: 7 additions & 4 deletions tests/test_importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@ def config(db):
name="IntField", field_type=forms.IntegerField, attrs={"min_value": 1}
)
fd2 = FieldDefinition.objects.create(
name="FloatField", field_type=forms.FloatField, attrs={"min_value": 1}
name="FloatField",
field_type=forms.FloatField,
attrs={"min_value": 1},
validation="""if (value % 2 == 0) {result="Insert an odd number"}""",
)
fs = Fieldset.objects.create(name="Fieldset")

FieldsetField.objects.create(name="int", field=fd1, fieldset=fs)
FieldsetField.objects.create(name="float", field=fd2, fieldset=fs)
data = [
{"int": 1, "float": 1.1},
{"int": 2, "float": 2.1},
{"int": 1, "float": 2.0},
{"int": 2, "float": 2.2},
{"int": -3, "float": 2.1},
]
return {"fs": fs, "data": data}
Expand All @@ -32,7 +35,7 @@ def test_validate_json(config):
fs: Fieldset = config["fs"]
result = validate_json(config["data"], fs.name)
assert result == [
"Ok",
{"float": ["Insert an odd number"]},
"Ok",
{"int": ["Ensure this value is greater than or equal to 1."]},
]
30 changes: 29 additions & 1 deletion tests/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,21 @@ def config(db):
fd2 = FieldDefinition.objects.create(
name="FloatField", field_type=forms.FloatField, attrs={"min_value": 1}
)
fd3 = FieldDefinition.objects.create(
name="Int1", field_type=forms.FloatField, attrs={"required": False}
)
fd4 = FieldDefinition.objects.create(
name="Int2",
field_type=forms.IntegerField,
attrs={"required": False},
regex=r"\d\d\d",
)
fs = Fieldset.objects.create(name="Fieldset")

FieldsetField.objects.create(name="int", field=fd1, fieldset=fs)
FieldsetField.objects.create(name="float", field=fd2, fieldset=fs)
FieldsetField.objects.create(name="int1", field=fd3, fieldset=fs)
FieldsetField.objects.create(name="int2", field=fd4, fieldset=fs)
return {"fs": fs}


Expand All @@ -39,4 +50,21 @@ def test_validate_fail(config):
fs: Fieldset = config["fs"]
with pytest.raises(ValidationError) as e:
fs.validate(data)
assert e.value.messages == ["Ensure this value is greater than or equal to 1."]

assert e.value.message_dict == {
"int": ["Ensure this value is greater than or equal to 1."]
}


def test_validate_regex(config):
# try to validate json formatted data against a FieldSet
data = {"int": 10, "float": 1.1, "str": "string", "int2": "1"}
fs: Fieldset = config["fs"]
with pytest.raises(ValidationError) as e:
fs.validate(data)
assert fs.errors == {"int2": ["Invalid format. Allowed Regex is '\\d\\d\\d'"]}
assert e.value.message_dict == {
"int2": ["Invalid format. Allowed Regex is '\\d\\d\\d'"]
}
data = {"int": 10, "float": 1.1, "str": "string", "int2": "111"}
assert fs.validate(data)

0 comments on commit 06afb89

Please sign in to comment.