Projet

Général

Profil

0001-misc-allow-rich-text-in-comments-27994.patch

Frédéric Péters, 21 décembre 2021 15:24

Télécharger (11,5 ko)

Voir les différences:

Subject: [PATCH] misc: allow "rich" text in comments (#27994)

 debian/control                                |  1 +
 tests/form_pages/test_all.py                  | 57 ++++++++++++++++++-
 tox.ini                                       |  1 +
 wcs/formdata.py                               |  8 +++
 wcs/formdef.py                                |  5 +-
 wcs/qommon/form.py                            | 12 ++++
 .../qommon/forms/widgets/mini-rich-text.html  | 10 ++++
 wcs/root.py                                   |  1 +
 wcs/variables.py                              |  5 +-
 wcs/workflows.py                              | 35 ++++++++++--
 10 files changed, 123 insertions(+), 12 deletions(-)
 create mode 100644 wcs/qommon/templates/qommon/forms/widgets/mini-rich-text.html
debian/control
31 31
         python3-unidecode,
32 32
         python3-uwsgidecorators,
33 33
         python3-vobject,
34
         python3-xstatic-godo,
34 35
         python3-xstatic-leaflet,
35 36
         python3-xstatic-leaflet-gesturehandling,
36 37
         uwsgi,
tests/form_pages/test_all.py
1730 1730
    assert 'form_comment' in resp.text  # makes sure user is treated as submitter
1731 1731
    resp.forms[0]['comment'] = 'hello world'
1732 1732
    resp = resp.forms[0].submit()
1733
    assert formdef.data_class().get(formdata_id).evolution[-1].comment == 'hello world'
1733
    assert formdef.data_class().get(formdata_id).evolution[-1].get_plain_text_comment() == 'hello world'
1734 1734

  
1735 1735
    # check we can also use it with lowercase letters.
1736 1736
    app = get_app(pub)
......
1824 1824
    assert 'form_comment' in resp.text  # makes sure user is treated as submitter
1825 1825
    resp.forms[0]['comment'] = 'hello world'
1826 1826
    resp = resp.forms[0].submit()
1827
    assert formdef.data_class().get(formdata_id).evolution[-1].comment == 'hello world'
1827
    assert formdef.data_class().get(formdata_id).evolution[-1].get_plain_text_comment() == 'hello world'
1828 1828

  
1829 1829
    # and check we can also get back to it as anonymous
1830 1830
    app = get_app(pub)
......
7600 7600

  
7601 7601
    resp.form['comment'] = 'plop'
7602 7602
    resp = resp.form.submit('button_x1').follow()
7603
    assert '<p>plop</p>' in resp.text
7603
    assert resp.pyquery('.comment').text() == 'plop'
7604 7604
    assert '<span class="status">Status2' in resp.text
7605 7605

  
7606 7606
    # no comment but no check
......
8483 8483

  
8484 8484
    formdata.refresh_from_storage()
8485 8485
    assert not formdata.workflow_data
8486

  
8487

  
8488
def test_rich_commentable_action(pub):
8489
    create_user(pub)
8490

  
8491
    formdef = create_formdef()
8492
    formdef.data_class().wipe()
8493
    formdef.roles = [logged_users_role().id]
8494
    formdef.store()
8495

  
8496
    wf = Workflow(name='status')
8497
    st1 = wf.add_status('Status1', 'st1')
8498

  
8499
    commentable = CommentableWorkflowStatusItem()
8500
    commentable.id = '_commentable'
8501
    commentable.by = [logged_users_role().id]
8502
    commentable.required = True
8503
    st1.items.append(commentable)
8504
    commentable.parent = st1
8505

  
8506
    choice = ChoiceWorkflowStatusItem()
8507
    choice.label = 'Submit'
8508
    choice.by = [logged_users_role().id]
8509
    choice.id = '_x1'
8510
    choice.status = st1.id
8511
    st1.items.append(choice)
8512
    choice.parent = st1
8513
    wf.store()
8514

  
8515
    formdef.workflow = wf
8516
    formdef.store()
8517

  
8518
    # comment
8519
    resp = login(get_app(pub), username='foo', password='foo').get('/test/')
8520
    resp = resp.form.submit('submit')  # -> validation page
8521
    resp = resp.form.submit('submit')  # -> submission
8522
    resp = resp.follow()
8523

  
8524
    resp.form['comment'] = '<p>hello <i>world</i></p>'
8525
    resp = resp.form.submit('button_x1').follow()
8526
    assert resp.pyquery('div.comment').text() == 'hello world'
8527
    assert '<p>hello <i>world</i></p>' in resp.text
8528

  
8529
    formdata = formdef.data_class().select()[0]
8530
    assert formdata.evolution[-1].parts[-1].comment == '<p>hello <i>world</i></p>'
8531

  
8532
    resp.form['comment'] = '<p>hello <script>evil</script></p>'
8533
    resp = resp.form.submit('button_x1').follow()
8534
    assert '<p>hello evil</p>' in resp.text
8535
    formdata = formdef.data_class().select()[0]
8536
    assert formdata.evolution[-1].parts[-1].comment == '<p>hello evil</p>'
tox.ini
36 36
    docutils
37 37
    langdetect
38 38
    git+https://git.entrouvert.org/debian/django-ckeditor.git
39
    git+https://git.entrouvert.org/godo.js.git
39 40
    django22: django>=2.2,<2.3
40 41
    django-ratelimit<3
41 42
    pyproj
wcs/formdata.py
186 186
        self._display_parts = l
187 187
        return self._display_parts
188 188

  
189
    def get_plain_text_comment(self):
190
        from .workflows import WorkflowCommentPart
191

  
192
        for part in reversed(self.parts or []):
193
            if isinstance(part, WorkflowCommentPart):
194
                return part.get_as_plain_text()
195
        return self.comment
196

  
189 197
    def get_json_export_dict(self, user, anonymise=False, include_files=True):
190 198
        data = {
191 199
            'time': datetime.datetime(*self.time[:6]) if self.time else None,
wcs/formdef.py
1536 1536
        if evo.status:
1537 1537
            details.append(_('Status'))
1538 1538
            details.append('  %s' % formdata.get_status_label())
1539
        if evo.comment:
1540
            details.append('\n%s\n' % evo.comment)
1539
        comment = evo.get_plain_text_comment()
1540
        if comment:
1541
            details.append('\n%s\n' % comment)
1541 1542
        return '\n\n----\n\n' + '\n'.join([str(x) for x in details])
1542 1543

  
1543 1544
    def is_of_concern_for_role_id(self, role_id):
wcs/qommon/form.py
2199 2199
                tags=self.ALL_TAGS,
2200 2200
                attributes=self.ALL_ATTRS,
2201 2201
                styles=self.ALL_STYLES,
2202
                strip=True,
2202 2203
                strip_comments=False,
2203 2204
            )
2204 2205
            if self.value.startswith('<br />'):
......
2235 2236
        )
2236 2237

  
2237 2238

  
2239
class MiniRichTextWidget(WysiwygTextWidget):
2240
    template_name = 'qommon/forms/widgets/mini-rich-text.html'
2241

  
2242
    ALL_TAGS = ['p', 'b', 'strong', 'i', 'em']
2243
    ALL_ATTRS = []
2244
    ALL_STYLES = []
2245

  
2246
    def add_media(self):
2247
        get_response().add_css_include('../xstatic/css/godo.css')
2248

  
2249

  
2238 2250
class TableWidget(CompositeWidget):
2239 2251
    readonly = False
2240 2252

  
wcs/qommon/templates/qommon/forms/widgets/mini-rich-text.html
1
{% extends "qommon/forms/widget.html" %}
2
{% block widget-control %}
3
<textarea style="display: none" id="form_{{widget.name}}" name="{{widget.name}}"
4
    {% for attr in widget.attrs.items %}{{attr.0}}="{{attr.1}}" {% endfor %}
5
    class="mini-rich-text">{{widget.value|default:""}}</textarea>
6
<script type="module">
7
import Godo from "/static/xstatic/js/godo.js";
8
new Godo(document.getElementById('form_{{widget.name}}'), {schema: 'basic'});
9
</script>
10
{% endblock %}
wcs/root.py
195 195
            'xstatic:jquery',
196 196
            'xstatic:jquery_ui',
197 197
            'xstatic:font_awesome',
198
            'xstatic:godo',
198 199
            'xstatic:opensans',
199 200
            'xstatic:leaflet',
200 201
            'xstatic:leaflet_gesturehandling',
wcs/variables.py
544 544

  
545 545
    @property
546 546
    def comment(self):
547
        if self._formdata.evolution and self._formdata.evolution[-1].comment:
548
            return self._formdata.evolution[-1].comment
547
        if self._formdata.evolution:
548
            latest_evolution = self._formdata.evolution[-1]
549
            return latest_evolution.get_plain_text_comment() or latest_evolution.comment
549 550
        return ''
550 551

  
551 552
    @property
wcs/workflows.py
18 18
import collections
19 19
import copy
20 20
import datetime
21
import html
21 22
import itertools
22 23
import os
23 24
import random
......
26 27
import xml.etree.ElementTree as ET
27 28

  
28 29
from django.utils.encoding import force_text
30
from django.utils.html import strip_tags
29 31
from quixote import get_publisher, get_request, get_response
30 32
from quixote.html import TemplateIO, htmltext
31 33

  
......
47 49
    ComputedExpressionWidget,
48 50
    ConditionWidget,
49 51
    Form,
52
    MiniRichTextWidget,
50 53
    SingleSelectWidget,
51 54
    SingleSelectWidgetWithOther,
52 55
    StringWidget,
......
2692 2695
        klass.init()
2693 2696

  
2694 2697

  
2698
class WorkflowCommentPart(EvolutionPart):
2699
    def __init__(self, comment, varname=None):
2700
        self.comment = comment
2701
        self.varname = varname
2702

  
2703
    def get_as_plain_text(self):
2704
        return html.unescape(strip_tags(self.comment))
2705

  
2706
    def view(self):
2707
        return htmltext('<div class="comment">%s</div>' % self.comment)
2708

  
2709

  
2695 2710
class CommentableWorkflowStatusItem(WorkflowStatusItem):
2696 2711
    description = _('Comment')
2697 2712
    key = 'commentable'
......
2721 2736
            else:
2722 2737
                title = self.label
2723 2738
            form.add(
2724
                TextWidget, 'comment', title=title, required=self.required, cols=40, rows=10, hint=self.hint
2739
                MiniRichTextWidget,
2740
                'comment',
2741
                title=title,
2742
                required=self.required,
2743
                cols=40,
2744
                rows=10,
2745
                hint=self.hint,
2725 2746
            )
2726 2747
            form.get_widget('comment').attrs['class'] = 'comment'
2727 2748
            if self.button_label == 0:
......
2733 2754

  
2734 2755
    def submit_form(self, form, formdata, user, evo):
2735 2756
        if form.get_widget('comment'):
2736
            evo.comment = form.get_widget('comment').parse()
2737
            if self.varname:
2738
                workflow_data = {'comment_%s' % self.varname: evo.comment}
2739
                formdata.update_workflow_data(workflow_data)
2757
            comment = form.get_widget('comment').parse()
2758
            if comment:
2759
                comment_part = WorkflowCommentPart(comment, varname=self.varname)
2760
                evo.add_part(comment_part)
2761
                if self.varname:
2762
                    formdata.update_workflow_data(
2763
                        {'comment_%s' % self.varname: comment_part.get_as_plain_text()}
2764
                    )
2740 2765

  
2741 2766
    def submit_admin_form(self, form):
2742 2767
        for f in self.get_parameters():
2743
-