Skip to content

Commit

Permalink
Fix passing type information to the frontend and reduce some complexi…
Browse files Browse the repository at this point in the history
…ty in the submission flow
  • Loading branch information
JeffersonBledsoe committed Jan 26, 2024
1 parent 38e9f9b commit 0251d4b
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 0251d4b

Please sign in to comment.