Skip to content

Commit

Permalink
Cannot set conditions with a '<' char when others are from type "file" (
Browse files Browse the repository at this point in the history
#2231)

* Sort the conditions in the edition view to prevent ZPublisher's error

When the value of a record received via POST should be tainted (e.g. when the
value contains the character '<'), ZPublisher (at least with 4.8.2) does it,
but it also does a deepcopy of the records processed earlier.

If one (or more) of these records processed earlier is from file type,
deepcopy cannot do the job and the following traceback arises:

```
Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 162, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 371, in publish_module
  Module ZPublisher.WSGIPublisher, line 232, in publish
  Module ZPublisher.HTTPRequest, line 737, in processInputs
  Module copy, line 163, in deepcopy\n  Module copy, line 230, in _deepcopy_list
  Module copy, line 190, in deepcopy\n  Module copy, line 334, in _reconstruct
  Module copy, line 163, in deepcopy\n  Module copy, line 257, in _deepcopy_dict
  Module copy, line 190, in deepcopy\n  Module copy, line 334, in _reconstruct
  Module copy, line 163, in deepcopy\n  Module copy, line 257, in _deepcopy_dict
  Module copy, line 182, in deepcopy\nTypeError: can't pickle cStringIO.StringO objects
```

As a result, the base error screen from plone "We’re sorry, but there seems to be an
error…" is rendered, but without any error or traceback information

Relevant debug info:

```
TypeError: can't pickle cStringIO.StringO objects
(Pdb++) where
[...]
-> self.execute()
[6]   /home/senaite/dev/buildout-cache/eggs/waitress-1.4.4-py2.7.egg/waitress/task.py(441)execute()
-> app_iter = self.channel.server.application(environ, start_response)
[7]   /home/senaite/dev/buildout-cache/eggs/Paste-3.5.0-py2.7.egg/paste/translogger.py(69)__call__()
-> return self.application(environ, replacement_start_response)
[8]   /home/senaite/dev/buildout-cache/eggs/Zope-4.8.2-py2.7.egg/ZPublisher/httpexceptions.py(30)__call__()
-> return self.application(environ, start_response)
[9]   /home/senaite/dev/buildout-cache/eggs/Zope-4.8.2-py2.7.egg/ZPublisher/WSGIPublisher.py(371)publish_module()
-> response = _publish(request, new_mod_info)
[10]   /home/senaite/dev/buildout-cache/eggs/Zope-4.8.2-py2.7.egg/ZPublisher/WSGIPublisher.py(232)publish()
-> request.processInputs()
[11] > /home/senaite/dev/buildout-cache/eggs/Zope-4.8.2-py2.7.egg/ZPublisher/HTTPRequest.py(738)processInputs()
-> reclist)
[12]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(163)deepcopy()
-> y = copier(x, memo)
[13]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(230)_deepcopy_list()
-> y.append(deepcopy(a, memo))
[14]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(190)deepcopy()
-> y = _reconstruct(x, rv, 1, memo)
[15]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(334)_reconstruct()
-> state = deepcopy(state, memo)
[16]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(163)deepcopy()
-> y = copier(x, memo)
[17]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(257)_deepcopy_dict()
-> y[deepcopy(key, memo)] = deepcopy(value, memo)
[18]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(190)deepcopy()
-> y = _reconstruct(x, rv, 1, memo)
[19]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(334)_reconstruct()
-> state = deepcopy(state, memo)
[20]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(163)deepcopy()
-> y = copier(x, memo)
[21]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(257)_deepcopy_dict()
-> y[deepcopy(key, memo)] = deepcopy(value, memo)
[22]   /home/senaite/dev/Python-2.7/lib/python2.7/copy.py(182)deepcopy()
-> rv = reductor(2)
(Pdb++) key
'conditions'
(Pdb++) item
'<'
(Pdb++) reclist[-1]
{'attachment': '', 'choices': '', 'description': '', 'required': '', 'title': 'Comentarios TGA', 'type': 'text', 'uid': '3939c785fcce47c9a3425d7e1d28aca2'}
(Pdb++) reclist[-2]
{'attachment': '', 'choices': '', 'description': 'Si el an\xc3\xa1lisis no se ajusta al esquema anterior, adjuntar fichero con las condiciones de ensayo', 'required': '', 'title': 'Fichero adjunto', 'type': 'file', 'uid': '3939c785fcce47c9a3425d7e1d28aca2', 'value': <ZPublisher.HTTPRequest.FileUpload object at 0x7fd973388b90>}
```

* Changelog
  • Loading branch information
xispa committed Jan 18, 2023
1 parent 54b8918 commit 7c4eb51
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Changelog
2.4.0 (unreleased)
------------------

- #2231 Cannot set conditions with a '<' char when others are from type "file"
- #2221 Migrate `ReferenceField` fields from setup types to `UIDReferenceField`
- #2228 Fix worksheet template title is not updated after template assignment
- #2226 Add setting to CC responsible persons in publication emails
Expand Down
23 changes: 21 additions & 2 deletions src/senaite/core/browser/modals/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,16 @@ def get_conditions(self):
uid = condition.get("value")
condition["attachment"] = self.get_attachment_info(uid)

return conditions
# Move conditions from "file" type to the end:
# Cannot set conditions with a '<' char when others are from file
# https://github.com/senaite/senaite.core/pulls/2231
def files_last(condition1, condition2):
type1 = condition1.get("type")
type2 = condition2.get("type")
if "file" not in [type1, type2]:
return 0
return 1 if type1 == "file" else -1
return sorted(conditions, cmp=files_last)

def get_attachment_info(self, uid):
attachment = api.get_object_by_uid(uid, default=None)
Expand All @@ -108,8 +117,18 @@ def handle_submit(self):
message = _("Not allowed to update conditions: {}").format(title)
return self.redirect(message=message, level="error")

# Update the conditions
# Sort the conditions as they were initially set
conditions = self.request.form.get("conditions", [])
original = self.get_analysis().getConditions(empties=True)
original = [cond.get("title") for cond in original]

def original_order(condition1, condition2):
index1 = original.index(condition1.get("title"))
index2 = original.index(condition2.get("title"))
return (index1 > index2) - (index1 < index2)
conditions = sorted(conditions, cmp=original_order)

# Update the conditions
analysis.setConditions(conditions)
message = _("Analysis conditions updated: {}").format(title)
return self.redirect(message=message)
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<colgroup>
<col style="width:20%"/>
</colgroup>
<tal:condition repeat="condition python:view.get_conditions()">
<tal:condition repeat="condition conditions">
<tr tal:define="required python:condition.get('required') == 'on';
is_checkbox python:condition.get('type') == 'checkbox';
required python:required and not is_checkbox;">
Expand Down

0 comments on commit 7c4eb51

Please sign in to comment.