misc: allow "rich" text in comments (#27994)
This commit is contained in:
parent
f0c3e9306f
commit
7338e136e5
|
@ -31,6 +31,7 @@ Depends: graphviz,
|
|||
python3-unidecode,
|
||||
python3-uwsgidecorators,
|
||||
python3-vobject,
|
||||
python3-xstatic-godo,
|
||||
python3-xstatic-leaflet,
|
||||
python3-xstatic-leaflet-gesturehandling,
|
||||
uwsgi,
|
||||
|
|
|
@ -1753,7 +1753,7 @@ def test_form_tracking_code(pub, nocache):
|
|||
assert 'form_comment' in resp.text # makes sure user is treated as submitter
|
||||
resp.forms[0]['comment'] = 'hello world'
|
||||
resp = resp.forms[0].submit()
|
||||
assert formdef.data_class().get(formdata_id).evolution[-1].comment == 'hello world'
|
||||
assert formdef.data_class().get(formdata_id).evolution[-1].get_plain_text_comment() == 'hello world'
|
||||
|
||||
# check we can also use it with lowercase letters.
|
||||
app = get_app(pub)
|
||||
|
@ -1847,7 +1847,7 @@ def test_form_tracking_code_as_user(pub, nocache):
|
|||
assert 'form_comment' in resp.text # makes sure user is treated as submitter
|
||||
resp.forms[0]['comment'] = 'hello world'
|
||||
resp = resp.forms[0].submit()
|
||||
assert formdef.data_class().get(formdata_id).evolution[-1].comment == 'hello world'
|
||||
assert formdef.data_class().get(formdata_id).evolution[-1].get_plain_text_comment() == 'hello world'
|
||||
|
||||
# and check we can also get back to it as anonymous
|
||||
app = get_app(pub)
|
||||
|
@ -7954,7 +7954,7 @@ def test_choice_button_ignore_form_errors(pub):
|
|||
|
||||
resp.form['comment'] = 'plop'
|
||||
resp = resp.form.submit('button_x1').follow()
|
||||
assert '<p>plop</p>' in resp.text
|
||||
assert resp.pyquery('.comment').text() == 'plop'
|
||||
assert '<span class="status">Status2' in resp.text
|
||||
|
||||
# no comment but no check
|
||||
|
@ -8837,3 +8837,54 @@ def test_workflow_form_structured_data(pub):
|
|||
|
||||
formdata.refresh_from_storage()
|
||||
assert not formdata.workflow_data
|
||||
|
||||
|
||||
def test_rich_commentable_action(pub):
|
||||
create_user(pub)
|
||||
|
||||
formdef = create_formdef()
|
||||
formdef.data_class().wipe()
|
||||
formdef.roles = [logged_users_role().id]
|
||||
formdef.store()
|
||||
|
||||
wf = Workflow(name='status')
|
||||
st1 = wf.add_status('Status1', 'st1')
|
||||
|
||||
commentable = CommentableWorkflowStatusItem()
|
||||
commentable.id = '_commentable'
|
||||
commentable.by = [logged_users_role().id]
|
||||
commentable.required = True
|
||||
st1.items.append(commentable)
|
||||
commentable.parent = st1
|
||||
|
||||
choice = ChoiceWorkflowStatusItem()
|
||||
choice.label = 'Submit'
|
||||
choice.by = [logged_users_role().id]
|
||||
choice.id = '_x1'
|
||||
choice.status = st1.id
|
||||
st1.items.append(choice)
|
||||
choice.parent = st1
|
||||
wf.store()
|
||||
|
||||
formdef.workflow = wf
|
||||
formdef.store()
|
||||
|
||||
# comment
|
||||
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
resp = resp.form.submit('submit') # -> submission
|
||||
resp = resp.follow()
|
||||
|
||||
resp.form['comment'] = '<p>hello <i>world</i></p>'
|
||||
resp = resp.form.submit('button_x1').follow()
|
||||
assert resp.pyquery('div.comment').text() == 'hello world'
|
||||
assert '<p>hello <i>world</i></p>' in resp.text
|
||||
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.evolution[-1].parts[-1].comment == '<p>hello <i>world</i></p>'
|
||||
|
||||
resp.form['comment'] = '<p>hello <script>evil</script></p>'
|
||||
resp = resp.form.submit('button_x1').follow()
|
||||
assert '<p>hello evil</p>' in resp.text
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.evolution[-1].parts[-1].comment == '<p>hello evil</p>'
|
||||
|
|
1
tox.ini
1
tox.ini
|
@ -36,6 +36,7 @@ deps =
|
|||
docutils
|
||||
langdetect
|
||||
git+https://git.entrouvert.org/debian/django-ckeditor.git
|
||||
git+https://git.entrouvert.org/godo.js.git
|
||||
django22: django>=2.2,<2.3
|
||||
django-ratelimit<3
|
||||
pyproj
|
||||
|
|
|
@ -186,6 +186,14 @@ class Evolution:
|
|||
self._display_parts = l
|
||||
return self._display_parts
|
||||
|
||||
def get_plain_text_comment(self):
|
||||
from .workflows import WorkflowCommentPart
|
||||
|
||||
for part in reversed(self.parts or []):
|
||||
if isinstance(part, WorkflowCommentPart):
|
||||
return part.get_as_plain_text()
|
||||
return self.comment
|
||||
|
||||
def get_json_export_dict(self, user, anonymise=False, include_files=True):
|
||||
data = {
|
||||
'time': datetime.datetime(*self.time[:6]) if self.time else None,
|
||||
|
|
|
@ -1661,8 +1661,9 @@ class FormDef(StorableObject):
|
|||
if evo.status:
|
||||
details.append(_('Status'))
|
||||
details.append(' %s' % formdata.get_status_label())
|
||||
if evo.comment:
|
||||
details.append('\n%s\n' % evo.comment)
|
||||
comment = evo.get_plain_text_comment()
|
||||
if comment:
|
||||
details.append('\n%s\n' % comment)
|
||||
return '\n\n----\n\n' + '\n'.join([str(x) for x in details])
|
||||
|
||||
def is_of_concern_for_role_id(self, role_id):
|
||||
|
|
|
@ -2269,6 +2269,7 @@ class WysiwygTextWidget(TextWidget):
|
|||
tags=self.ALL_TAGS,
|
||||
attributes=self.ALL_ATTRS,
|
||||
styles=self.ALL_STYLES,
|
||||
strip=True,
|
||||
strip_comments=False,
|
||||
)
|
||||
if self.value.startswith('<br />'):
|
||||
|
@ -2305,6 +2306,17 @@ class WysiwygTextWidget(TextWidget):
|
|||
)
|
||||
|
||||
|
||||
class MiniRichTextWidget(WysiwygTextWidget):
|
||||
template_name = 'qommon/forms/widgets/mini-rich-text.html'
|
||||
|
||||
ALL_TAGS = ['p', 'b', 'strong', 'i', 'em']
|
||||
ALL_ATTRS = []
|
||||
ALL_STYLES = []
|
||||
|
||||
def add_media(self):
|
||||
get_response().add_css_include('../xstatic/css/godo.css')
|
||||
|
||||
|
||||
class TableWidget(CompositeWidget):
|
||||
readonly = False
|
||||
|
||||
|
|
|
@ -675,8 +675,13 @@ div.form-preview > br {
|
|||
clear: both; /* stop grid */
|
||||
}
|
||||
|
||||
div.godo {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
div.TextWidget textarea:focus,
|
||||
div.widget input[type="text"]:focus {
|
||||
div.widget input[type="text"]:focus,
|
||||
div.godo--editor.ProseMirror-focused {
|
||||
box-shadow: 0 0 0px 1px #1999cd;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{% extends "qommon/forms/widget.html" %}
|
||||
{% block widget-control %}
|
||||
<textarea style="display: none" id="form_{{widget.name}}" name="{{widget.name}}"
|
||||
{% for attr in widget.attrs.items %}{{attr.0}}="{{attr.1}}" {% endfor %}
|
||||
class="mini-rich-text">{{widget.value|default:""}}</textarea>
|
||||
<script type="module">
|
||||
import Godo from "/static/xstatic/js/godo.js";
|
||||
new Godo(document.getElementById('form_{{widget.name}}'), {schema: 'basic'});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -195,6 +195,7 @@ class StaticsDirectory(Directory):
|
|||
'xstatic:jquery',
|
||||
'xstatic:jquery_ui',
|
||||
'xstatic:font_awesome',
|
||||
'xstatic:godo',
|
||||
'xstatic:opensans',
|
||||
'xstatic:leaflet',
|
||||
'xstatic:leaflet_gesturehandling',
|
||||
|
|
|
@ -552,8 +552,9 @@ class LazyFormData(LazyFormDef):
|
|||
|
||||
@property
|
||||
def comment(self):
|
||||
if self._formdata.evolution and self._formdata.evolution[-1].comment:
|
||||
return self._formdata.evolution[-1].comment
|
||||
if self._formdata.evolution:
|
||||
latest_evolution = self._formdata.evolution[-1]
|
||||
return latest_evolution.get_plain_text_comment() or latest_evolution.comment
|
||||
return ''
|
||||
|
||||
@property
|
||||
|
|
|
@ -18,6 +18,7 @@ import base64
|
|||
import collections
|
||||
import copy
|
||||
import datetime
|
||||
import html
|
||||
import itertools
|
||||
import os
|
||||
import random
|
||||
|
@ -26,6 +27,7 @@ import uuid
|
|||
import xml.etree.ElementTree as ET
|
||||
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.html import strip_tags
|
||||
from quixote import get_publisher, get_request, get_response
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
|
@ -47,6 +49,7 @@ from .qommon.form import (
|
|||
ComputedExpressionWidget,
|
||||
ConditionWidget,
|
||||
Form,
|
||||
MiniRichTextWidget,
|
||||
SingleSelectWidget,
|
||||
SingleSelectWidgetWithOther,
|
||||
StringWidget,
|
||||
|
@ -2717,6 +2720,18 @@ def register_item_class(klass):
|
|||
klass.init()
|
||||
|
||||
|
||||
class WorkflowCommentPart(EvolutionPart):
|
||||
def __init__(self, comment, varname=None):
|
||||
self.comment = comment
|
||||
self.varname = varname
|
||||
|
||||
def get_as_plain_text(self):
|
||||
return html.unescape(strip_tags(self.comment))
|
||||
|
||||
def view(self):
|
||||
return htmltext('<div class="comment">%s</div>' % self.comment)
|
||||
|
||||
|
||||
class CommentableWorkflowStatusItem(WorkflowStatusItem):
|
||||
description = _('Comment')
|
||||
key = 'commentable'
|
||||
|
@ -2746,7 +2761,13 @@ class CommentableWorkflowStatusItem(WorkflowStatusItem):
|
|||
else:
|
||||
title = self.label
|
||||
form.add(
|
||||
TextWidget, 'comment', title=title, required=self.required, cols=40, rows=10, hint=self.hint
|
||||
MiniRichTextWidget,
|
||||
'comment',
|
||||
title=title,
|
||||
required=self.required,
|
||||
cols=40,
|
||||
rows=10,
|
||||
hint=self.hint,
|
||||
)
|
||||
form.get_widget('comment').attrs['class'] = 'comment'
|
||||
if self.button_label == 0:
|
||||
|
@ -2758,10 +2779,14 @@ class CommentableWorkflowStatusItem(WorkflowStatusItem):
|
|||
|
||||
def submit_form(self, form, formdata, user, evo):
|
||||
if form.get_widget('comment'):
|
||||
evo.comment = form.get_widget('comment').parse()
|
||||
if self.varname:
|
||||
workflow_data = {'comment_%s' % self.varname: evo.comment}
|
||||
formdata.update_workflow_data(workflow_data)
|
||||
comment = form.get_widget('comment').parse()
|
||||
if comment:
|
||||
comment_part = WorkflowCommentPart(comment, varname=self.varname)
|
||||
evo.add_part(comment_part)
|
||||
if self.varname:
|
||||
formdata.update_workflow_data(
|
||||
{'comment_%s' % self.varname: comment_part.get_as_plain_text()}
|
||||
)
|
||||
|
||||
def submit_admin_form(self, form):
|
||||
for f in self.get_parameters():
|
||||
|
|
Loading…
Reference in New Issue