Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Likert layout #426

Closed
wants to merge 14 commits into from
7 changes: 7 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Changelog
- Add plone.shortname behaviour to EasyForm type.
[ThibautBorn]

- fix (de)serialize issues with Labels [gotcha]


4.2.0 (2024-03-15)
------------------
Expand Down Expand Up @@ -35,6 +37,11 @@ Changelog
- check for "collective.easyform.DownloadSavedInput" permission, before including the saved data in serializer.
[MrTango]

- Add filesize upload limit. [ThibautBorn]

- update Dutch translations [ThibautBorn]



4.1.4 (2023-07-27)
------------------
Expand Down
7 changes: 7 additions & 0 deletions src/collective/easyform/browser/fields.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@
class="plone.schemaeditor.browser.schema.add_fieldset.FieldsetAddFormPage"
layer="..interfaces.IEasyFormLayer"
/>
<browser:page
name="delete-fieldset"
for="collective.easyform.interfaces.IEasyFormFieldsContext"
permission="cmf.ModifyPortalContent"
class="plone.schemaeditor.browser.schema.delete_fieldset.DeleteFieldset"
layer="..interfaces.IEasyFormLayer"
/>
<browser:page
name="edit"
for="collective.easyform.interfaces.IEasyFormFieldContext"
Expand Down
9 changes: 5 additions & 4 deletions src/collective/easyform/browser/likert_input.pt
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
tal:omit-tag="">
<table class="likert listing" tal:attributes="class string:${view/klass} listing;">
<table class="likert listing" tal:attributes="class string:${view/klass} listing table-bordered table-striped;">
<tbody><tr>
<th>&nbsp;</th>
<th tal:repeat="answer view/field/answers" tal:content="answer" />
<th tal:repeat="answer view/field/answers" tal:attributes="class string: text-center;" tal:content="answer" />
</tr>
<tr tal:repeat="question view/field/questions" tal:attributes="class python:'even' if repeat.question.even else 'odd'">
<td>
<td tal:attributes="scope string: col; class string: col-3;">
<span tal:content="question">Question Number One</span>
<input name="field-empty-marker" type="hidden" value="1"
tal:attributes="name string:${view/name}-${repeat/question/index}-empty-marker" />
<td tal:repeat="answer view/field/answers"
onclick="jQuery(event.target).children('input').click()">
onclick="jQuery(event.target).children('input').click()"
tal:attributes="scope string: col; class string: col-1 text-center;">
<input type="radio"
tal:define="answer_index repeat/answer/index;
question_index repeat/question/index;
Expand Down
2 changes: 1 addition & 1 deletion src/collective/easyform/interfaces/easyform.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def default_thanksdescription(context):
@zope.interface.provider(zope.schema.interfaces.IContextAwareDefaultFactory)
def default_actions(context):
"""Default mail body for mailer action.
Acquire 'mail_body_default.pt' or return hard coded default
Acquire 'easyform_default_actions.xml' or return hard coded default
"""
portal = api.portal.get()
default_actions = portal.restrictedTraverse(
Expand Down
8 changes: 8 additions & 0 deletions src/collective/easyform/migration/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ def actions_model(ploneformgen):
)
continue

# remove outdated/deleted fields from the showfields list for saveddata adapters
# otherwise a keyerror will be thrown when viewing the data in easyform
if type_.name == 'collective.easyform.actions.SaveData':
filteredFields = []
available_fields = [field.getName() for field in ploneformgen.fgFields()]
filteredFields = [fieldname for fieldname in properties.get('showFields') if fieldname in available_fields]
properties['showFields'] = filteredFields

field = type_.handler(schema, type_.name, actionname, properties)

for name, value in properties.items():
Expand Down
7 changes: 4 additions & 3 deletions src/collective/easyform/migration/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def append_node(field, name, value):
name = "{{{}}}{}".format(ns, name)
node = etree.SubElement(field, name)
if isinstance(value, (list, tuple)):
value = u" ".join(value)
value = u"\n".join(value)
node.text = value
return node

Expand Down Expand Up @@ -97,8 +97,9 @@ def append_vocab_node(field, name, value):


def append_default_node(field, name, value):
if isinstance(value, list):
return
# commented out, as it is unclear why we wouldn't set default lists?
# if isinstance(value, list):
# return
if field.get("type") == "collective.easyform.fields.RichLabel":
append_node(field, "rich_label", value)
else:
Expand Down
73 changes: 54 additions & 19 deletions src/collective/easyform/serializer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from collective.easyform.api import get_schema
from collective.easyform.config import DOWNLOAD_SAVED_PERMISSION
from collective.easyform.interfaces import IEasyForm
from collective.easyform.interfaces import ILabel
from collective.easyform.interfaces import ISaveData
from Products.CMFPlone.utils import safe_unicode

Expand All @@ -44,22 +45,22 @@ def serializeSavedData(self, result):
storage = dict()
actions = getFieldsInOrder(get_actions(self.context))

AllFieldsinOrder = getFieldsInOrder(get_schema(self.context))
included_columns_in_savedata = []
for column, field in AllFieldsinOrder:
if "label" not in field.__str__().lower():
included_columns_in_savedata.append(column)
allFieldsInOrder = getFieldsInOrder(get_schema(self.context))
included_columns_in_savedata = [
column
for column, field in allFieldsInOrder
# Labels must be excluded to avoid column mismatch
if not ILabel.providedBy(field)
]
included_columns_in_savedata.sort()

for name, action in actions:
if ISaveData.providedBy(action):
serializeable = dict()
storage[name] = serializeable
for id, data in action.getSavedFormInputItems():
column_names = list(data.keys())
column_names.remove("id")
column_names.sort()
if column_names != included_columns_in_savedata:
relevant_columns = columns_to_serialize(action, data)
if not action.showFields and relevant_columns != included_columns_in_savedata:
logger.warning(
"Skipped Saveddata row because of mismatch witch current fields in %s",
self.context.absolute_url(),
Expand All @@ -80,6 +81,26 @@ def serializeSavedData(self, result):
result["savedDataStorage"] = storage


def columns_to_serialize(action, data):
if action.showFields:
return action.showFields
else:
column_names = list(data.keys())
column_names.remove("id")
column_names = filter_extradata(column_names, action)
column_names.sort()
return column_names


def filter_extradata(column_names, action):
if not action.ExtraData:
return column_names
for extra in action.ExtraData:
if extra in column_names:
column_names.remove(extra)
return column_names


def convertBeforeSerialize(value):
if isinstance(value, (datetime, date)):
return value.isoformat()
Expand Down Expand Up @@ -115,27 +136,41 @@ def deserializeSavedData(self, data):
actions = getFieldsInOrder(get_actions(self.context))
schema = get_schema(self.context)

AllFieldsinOrder = schema.namesAndDescriptions()
included_columns_in_savedata = []
for column, field in AllFieldsinOrder:
if "label" not in field.__str__().lower():
included_columns_in_savedata.append(column)

for name, action in actions:
if ISaveData.providedBy(action) and name in storage:
relevant_columns = columns_to_deserialize(action, schema)
savedData = storage[name]
for key, value in savedData.items():
for name in included_columns_in_savedata: # schema.names():
value[name] = convertAfterDeserialize(
schema[name], value[name]
)
for name in schema.names():
if name in relevant_columns:
value[name] = convertAfterDeserialize(
schema[name], value[name]
)
elif name in value:
del value[name]
action.setDataRow(int(key), value)


def columns_to_deserialize(action, schema):
if action.showFields:
return action.showFields
else:
return [
column
for column, field in schema.namesAndDescriptions()
# Labels must be excluded
# because their column are not included in serialized data.
if not ILabel.providedBy(field)
]


def convertAfterDeserialize(field, value):
if ISet.providedBy(field):
return set(value)
elif IDate.providedBy(field) or IDatetime.providedBy(field):
# empty dates are saved as empty string which breaks the parser
if not value:
return None
return parser.parse(value)
elif IRichText.providedBy(field):
return RichTextValue(value)
Expand Down
24 changes: 24 additions & 0 deletions src/collective/easyform/tests/fixtures/fieldset_file_maxsize.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<model xmlns="http://namespaces.plone.org/supermodel/schema"
xmlns:easyform="http://namespaces.plone.org/supermodel/easyform"
xmlns:form="http://namespaces.plone.org/supermodel/form"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
xmlns:indexer="http://namespaces.plone.org/supermodel/indexer"
xmlns:lingua="http://namespaces.plone.org/supermodel/lingua"
xmlns:marshal="http://namespaces.plone.org/supermodel/marshal"
xmlns:security="http://namespaces.plone.org/supermodel/security"
xmlns:users="http://namespaces.plone.org/supermodel/users"
>
<schema>
<fieldset label="Fieldset 1"
name="fs1"
>
<field name="file1"
type="plone.namedfile.field.NamedFile"
>
<description />
<required>False</required>
<title>Upload</title>
</field>
</fieldset>
</schema>
</model>
Loading
Loading