Skip to content

Commit

Permalink
Merge branch 'field-validation' into deploy
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffersonBledsoe committed Jan 26, 2024
2 parents be16385 + 0251d4b commit b515794
Show file tree
Hide file tree
Showing 7 changed files with 83 additions and 131 deletions.
82 changes: 0 additions & 82 deletions src/collective/volto/formsupport/restapi/deserializer/__init__.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,10 @@
from plone.base.interfaces import IPloneSiteRoot
from plone.restapi.behaviors import IBlocks
from plone.restapi.interfaces import IBlockFieldDeserializationTransformer

# from plone.restapi.interfaces import IBlockFieldSerializationTransformer
from zope.component import adapter
from zope.interface import implementer
from zope.publisher.interfaces.browser import IBrowserRequest

from collective.volto.formsupport.validation import getValidations

IGNORED_VALIDATION_DEFINITION_ARGUMENTS = [
"title",
"description",
"name",
"errmsg",
"regex",
"regex_strings",
"ignore",
"_internal_type",
]

python_type_to_volto_type_mapping = {
"int": "integer",
"float": "number",
"bool": "boolean",
}


@adapter(IBlocks, IBrowserRequest)
class FormBlockDeserializerBase:
Expand All @@ -43,69 +22,8 @@ def _process_data(
self,
data,
):
# Field is the full field definition
for index, field in enumerate(data.get("subblocks", [])):
if len(field.get("validations", [])) > 0:
data["subblocks"][index] = self._update_validations(field)

return data

def _update_validations(self, field):
validation_ids_on_field = field.get("validations")
all_validation_settings = field.get("validationSettings")

if not validation_ids_on_field:
field["validationSettings"] = {}
return field

# The settings were collapsed to a single control on the frontend, we need to find the validation it was for and tidy things up before continuing
all_setting_ids = all_validation_settings.keys()
top_level_setting_ids = []
for validation_id in validation_ids_on_field:
id_to_check = f"{validation_id}-"
for setting_id in all_setting_ids:
if setting_id.startswith(id_to_check):
top_level_setting_ids.append(setting_id)
for top_level_setting_id in top_level_setting_ids:
validation_id, setting_id = top_level_setting_id.split("-")
all_validation_settings[validation_id][
setting_id
] = all_validation_settings[top_level_setting_id]

# update the internal definitions for the field settings
for validation_id in validation_ids_on_field:
validation_to_update = [
validation
for validation in getValidations()
if validation[0] == validation_id
][0][1]

validation_settings = all_validation_settings.get(validation_id)

if validation_settings:
for setting_name, setting_value in all_validation_settings[
validation_id
].items():
if setting_name in IGNORED_VALIDATION_DEFINITION_ARGUMENTS:
continue
validation_to_update._settings[setting_name] = setting_value

field["validationSettings"][validation_id] = {
k: v
for k, v in validation_to_update.settings.items()
if k not in IGNORED_VALIDATION_DEFINITION_ARGUMENTS
}

# Remove any old settings
keys_to_delete = []
for key in all_validation_settings.keys():
if key not in validation_ids_on_field:
keys_to_delete.append(key)
for key in keys_to_delete:
del all_validation_settings[key]

return field


@implementer(IBlockFieldDeserializationTransformer)
@adapter(IBlocks, IBrowserRequest)
Expand Down
37 changes: 4 additions & 33 deletions src/collective/volto/formsupport/restapi/serializer/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,7 @@
ICaptchaSupport,
ICollectiveVoltoFormsupportLayer,
)

IGNORED_VALIDATION_DEFINITION_ARGUMENTS = [
"title",
"description",
"name",
"errmsg",
"regex",
"regex_strings",
"ignore",
"_internal_type",
]
from collective.volto.formsupport.validation import get_validation_information


class FormSerializer(object):
Expand Down Expand Up @@ -51,33 +41,14 @@ def __call__(self, value):
if attachments_limit:
value["attachments_limit"] = attachments_limit

for index, field in enumerate(value.get("subblocks", [])):
if field.get("validationSettings"):
value["subblocks"][index] = self._expand_validation_field(field)
# Add information on the settings for validations to the response
validation_settings = get_validation_information()
value["validationSettings"] = validation_settings

if api.user.has_permission("Modify portal content", obj=self.context):
return value
return {k: v for k, v in value.items() if not k.startswith("default_")}

def _expand_validation_field(self, field):
"""Adds the individual validation settings to the `validationSettings` key in the format `{validation_id}-{setting_name}`"""
validation_settings = field.get("validationSettings")
settings_to_add = {}
for validation_id, settings in validation_settings.items():
if not isinstance(settings, dict):
continue
cleaned_settings = {
f"{validation_id}-{setting_name}": val
for setting_name, val in settings.items()
if setting_name not in IGNORED_VALIDATION_DEFINITION_ARGUMENTS
}

if cleaned_settings:
settings_to_add = {**settings_to_add, **cleaned_settings}
field["validationSettings"] = {**validation_settings, **settings_to_add}

return field


@implementer(IBlockFieldSerializationTransformer)
@adapter(IBlocks, ICollectiveVoltoFormsupportLayer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class Field:
def __init__(self, field_data):
def _attribute(attribute_name):
def _attribute(attribute_name: str):
setattr(self, attribute_name, field_data.get(attribute_name))

_attribute("field_type")
Expand All @@ -26,9 +26,6 @@ def _attribute(attribute_name):
self._custom_field_id = field_data.get("custom_field_id")
self._label = field_data.get("label")
self._field_id = field_data.get("field_id", "")
self._validations = field_data.get(
"validations", []
) # No need to expose the available validations

@property
def value(self):
Expand Down Expand Up @@ -63,16 +60,16 @@ def send_in_email(self):
return True

def validate(self):
# Products.validation isn't included by default
# Making sure we've got a validation that actually exists.
available_validations = [
validation
for validationId, validation in getValidations()
if validationId in self._validations
if validationId in self.validations.keys()
]

errors = {}
for validation in available_validations:
error = validation(self._value)
error = validation(self._value, **self.validations.get(validation._name))
if error:
match_result = validation_message_matcher.match(error)
# We should be able to clean up messages that follow the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,26 @@ def reply(self):
if field.get("id", field.get("field_id")) == submitted_field.get(
"field_id"
):
validation_ids_to_apply = field.get("validations")
validations_for_field = {}
for validation_and_setting_id, setting_value in field.get(
"validationSettings"
).items():
validation_id, setting_id = validation_and_setting_id.split("-")
if validation_id not in validation_ids_to_apply:
continue
if validation_id not in validations_for_field:
validations_for_field[validation_id] = {}
validations_for_field[validation_id][setting_id] = setting_value
fields_data.append(
{
**field,
**submitted_field,
"display_value_mapping": field.get("display_values"),
"custom_field_id": self.block.get(field["field_id"]),
# We're straying from how validations are serialized and deserialized here to make our lives easier.
# Let's use a dictionary of {'validation_id': {'setting_id': 'setting_value'}} when working inside fields for simplicity.
"validations": validations_for_field,
}
)
self.fields = construct_fields(fields_data)
Expand Down
52 changes: 52 additions & 0 deletions src/collective/volto/formsupport/validation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
baseValidators = None


IGNORED_VALIDATION_DEFINITION_ARGUMENTS = [
"title",
"description",
"name",
"errmsg",
"regex",
"regex_strings",
"ignore",
"_internal_type",
]


class IFieldValidator(Interface):
"""Base marker for collective.volto.formsupport field validators."""

Expand Down Expand Up @@ -48,6 +60,46 @@ def getValidations():
return utils


PYTHON_TYPE_SCHEMA_TYPE_MAPPING = {
"bool": "boolean",
"date": "date",
"dict": "obj",
"float": "number",
"int": "integer",
"list": "array",
"str": "string",
"time": "datetime",
}


def get_validation_information():
"""Adds the individual validation settings to the `validationSettings` key in the format `{validation_id}-{setting_name}`"""
settings_to_add = {}

for validation_id, validation in getValidations():
settings = validation.settings
if not isinstance(settings, dict) or not settings:
# We don't have any settings, skip including it
continue
cleaned_settings = {
setting_name: val
for setting_name, val in settings.items()
if setting_name not in IGNORED_VALIDATION_DEFINITION_ARGUMENTS
}

for setting_id, setting_value in cleaned_settings.items():
settings_to_add[f"{validation_id}-{setting_id}"] = {
"validation_title": getattr(settings, "title", validation_id),
"title": setting_id,
"type": PYTHON_TYPE_SCHEMA_TYPE_MAPPING.get(
type(setting_value).__name__, "string"
),
"default": setting_value,
}

return settings_to_add


@provider(IVocabularyFactory)
def ValidatorsVocabularyFactory(context, **rest):
"""Field validators vocabulary"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@
@implementer(IValidator)
class CharactersValidator:
def __init__(self, name, title="", description="", characters=0, _internal_type=""):
""" "Unused properties are for default values and type information"""
self.name = name
self.title = title or name
self.description = description
self.characters = characters
self._internal_type = _internal_type
# Default values
self.characters = characters

def __call__(self, value="", *args, **kwargs):
characters = (
int(self.characters)
if isinstance(self.characters, str)
else self.characters
)
characters = kwargs.get("characters", self.characters)
characters = int(characters) if isinstance(characters, str) else characters

if self._internal_type == "max":
if not value:
return
Expand Down
6 changes: 3 additions & 3 deletions src/collective/volto/formsupport/validation/definition.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@ def __init__(self, validator):

def __call__(self, value, **kwargs):
"""Allow using the class directly as a validator"""
return self.validate(value, **kwargs)
return self.validate(value=value, **kwargs)

@property
def settings(self):
return self._settings

@settings.setter
def settings(self, value):
self._value = value
self._settings = value

def validate(self, value, **kwargs):
if value is None:
# Let the system for required take care of None values
return
res = validation(self._name, value, **self.settings, **kwargs)
res = validation(self._name, value, **kwargs)
if res != 1:
return res

0 comments on commit b515794

Please sign in to comment.