Projet

Général

Profil

0001-backoffice-add-a-deprecations-report-page-58799.patch

Frédéric Péters, 04 avril 2022 13:27

Télécharger (37,9 ko)

Voir les différences:

Subject: [PATCH] backoffice: add a deprecations report page (#58799)

 tests/admin_pages/test_deprecations.py        | 221 +++++++++++++++++
 wcs/backoffice/deprecations.py                | 225 ++++++++++++++++++
 wcs/backoffice/studio.py                      |   6 +-
 wcs/blocks.py                                 |   3 +
 wcs/data_sources.py                           |   7 +-
 wcs/fields.py                                 |   6 +
 wcs/formdef.py                                |   3 +
 wcs/publisher.py                              |  10 +
 wcs/qommon/static/css/dc2/admin.scss          |  24 ++
 wcs/qommon/template.py                        |   3 +
 .../wcs/backoffice/deprecations.html          |  41 ++++
 wcs/templates/wcs/backoffice/studio.html      |   7 +-
 wcs/wf/backoffice_fields.py                   |   5 +
 wcs/wf/create_formdata.py                     |   6 +
 wcs/wf/dispatch.py                            |   4 +
 wcs/wf/export_to_model.py                     |   4 +
 wcs/wf/external_workflow.py                   |   4 +
 wcs/wf/geolocate.py                           |   4 +
 wcs/wf/jump.py                                |   4 +
 wcs/wf/notification.py                        |   4 +
 wcs/wf/profile.py                             |   5 +
 wcs/wf/redirect_to_url.py                     |   4 +
 wcs/wf/sendmail.py                            |   8 +
 wcs/wf/wscall.py                              |   8 +
 wcs/workflows.py                              |  24 ++
 wcs/wscalls.py                                |   9 +
 26 files changed, 644 insertions(+), 5 deletions(-)
 create mode 100644 tests/admin_pages/test_deprecations.py
 create mode 100644 wcs/backoffice/deprecations.py
 create mode 100644 wcs/templates/wcs/backoffice/deprecations.html
tests/admin_pages/test_deprecations.py
1
import os
2

  
3
import pytest
4

  
5
from wcs import fields
6
from wcs.blocks import BlockDef
7
from wcs.carddef import CardDef
8
from wcs.data_sources import NamedDataSource
9
from wcs.formdef import FormDef
10
from wcs.qommon.http_request import HTTPRequest
11
from wcs.wf.backoffice_fields import SetBackofficeFieldsWorkflowStatusItem
12
from wcs.wf.create_formdata import CreateFormdataWorkflowStatusItem, Mapping
13
from wcs.wf.export_to_model import ExportToModel
14
from wcs.wf.external_workflow import ExternalWorkflowGlobalAction
15
from wcs.wf.geolocate import GeolocateWorkflowStatusItem
16
from wcs.wf.jump import JumpWorkflowStatusItem
17
from wcs.wf.notification import SendNotificationWorkflowStatusItem
18
from wcs.wf.profile import UpdateUserProfileStatusItem
19
from wcs.wf.redirect_to_url import RedirectToUrlWorkflowStatusItem
20
from wcs.wf.sendmail import SendmailWorkflowStatusItem
21
from wcs.wf.wscall import WebserviceCallStatusItem
22
from wcs.workflows import (
23
    ChoiceWorkflowStatusItem,
24
    DisplayMessageWorkflowStatusItem,
25
    SendSMSWorkflowStatusItem,
26
    Workflow,
27
)
28
from wcs.wscalls import NamedWsCall
29

  
30
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
31
from .test_all import create_superuser
32

  
33

  
34
@pytest.fixture
35
def pub(request):
36
    pub = create_temporary_pub(sql_mode=True)
37

  
38
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
39
    pub.set_app_dir(req)
40
    pub.cfg['identification'] = {'methods': ['password']}
41
    pub.cfg['language'] = {'language': 'en'}
42
    pub.write_cfg()
43

  
44
    if os.path.exists(os.path.join(pub.app_dir, 'deprecations.json')):
45
        os.remove(os.path.join(pub.app_dir, 'deprecations.json'))
46

  
47
    BlockDef.wipe()
48
    CardDef.wipe()
49
    FormDef.wipe()
50
    NamedDataSource.wipe()
51
    NamedWsCall.wipe()
52
    Workflow.wipe()
53

  
54
    return pub
55

  
56

  
57
def teardown_module(module):
58
    clean_temporary_pub()
59

  
60

  
61
def test_no_deprecations(pub):
62
    create_superuser(pub)
63
    app = login(get_app(pub))
64
    # first time, it's a redirect to the scanning job
65
    resp = app.get('/backoffice/studio/deprecations/', status=302)
66
    resp = resp.follow()
67
    resp = resp.click('Go to deprecation report')
68
    # second time, the page stays on
69
    resp = app.get('/backoffice/studio/deprecations/', status=200)
70
    assert 'No deprecated items were found on this site.' in resp.text
71

  
72

  
73
def test_deprecations(pub):
74
    create_superuser(pub)
75

  
76
    formdef = FormDef()
77
    formdef.name = 'foobar'
78
    formdef.fields = [
79
        fields.PageField(id='1', label='page1', type='page', condition={'type': 'python', 'value': 'True'}),
80
        fields.StringField(id='2', label='python_prefill', prefill={'type': 'formula', 'value': '1 + 2'}),
81
        fields.StringField(
82
            id='3', label='ezt_prefill', prefill={'type': 'string', 'value': '[form_var_test]'}
83
        ),
84
        fields.StringField(id='4', label='jsonp_data', data_source={'type': 'jsonp', 'value': 'xxx'}),
85
        fields.StringField(id='5', label='ezt_in_datasource', data_source={'type': 'json', 'value': '[xxx]'}),
86
        fields.CommentField(id='6', label='[ezt] in label', type='comment'),
87
        fields.CommentField(id='7', label='{{script.usage}} in template', type='comment'),
88
        fields.PageField(
89
            id='10',
90
            label='page2',
91
            type='page',
92
            post_conditions=[
93
                {'condition': {'type': 'python', 'value': 'False'}, 'error_message': 'You shall not pass.'}
94
            ],
95
        ),
96
    ]
97
    formdef.store()
98

  
99
    workflow = Workflow(name='test')
100
    st0 = workflow.add_status('Status0', 'st0')
101

  
102
    display = DisplayMessageWorkflowStatusItem()
103
    display.message = 'message with [ezt] info'
104
    display.parent = st0
105
    st0.items.append(display)
106

  
107
    wscall = WebserviceCallStatusItem()
108
    wscall.id = '_wscall'
109
    wscall.varname = 'xxx'
110
    wscall.url = 'http://remote.example.net/xml'
111
    wscall.post_data = {'str': 'abcd', 'evalme': '=form_number'}
112
    wscall.parent = st0
113
    st0.items.append(wscall)
114

  
115
    sendsms = SendSMSWorkflowStatusItem()
116
    sendsms.id = '_sendsms'
117
    sendsms.to = 'xxx'
118
    sendsms.parent = st0
119
    st0.items.append(sendsms)
120

  
121
    accept = ChoiceWorkflowStatusItem()
122
    accept.id = '_choice'
123
    accept.label = '[plop]'
124
    accept.parent = st0
125
    st0.items.append(accept)
126

  
127
    item = SetBackofficeFieldsWorkflowStatusItem()
128
    item.id = '_item'
129
    item.fields = [{'field_id': 'bo1', 'value': '=form_var_foo'}]
130
    item.parent = st0
131
    st0.items.append(item)
132

  
133
    create_formdata = CreateFormdataWorkflowStatusItem()
134
    create_formdata.id = '_create_formdata'
135
    create_formdata.varname = 'resubmitted'
136
    create_formdata.mappings = [
137
        Mapping(field_id='0', expression='=form_var_toto_string'),
138
    ]
139
    create_formdata.parent = st0
140
    st0.items.append(create_formdata)
141

  
142
    item = UpdateUserProfileStatusItem()
143
    item.id = '_item'
144
    item.fields = [{'field_id': '__email', 'value': '=form_var_foo'}]
145
    item.parent = st0
146
    st0.items.append(item)
147

  
148
    sendmail = SendmailWorkflowStatusItem()
149
    sendmail.id = '_sendmail'
150
    sendmail.to = ['=plop']
151
    sendmail.parent = st0
152
    st0.items.append(sendmail)
153

  
154
    for klass in (
155
        ExportToModel,
156
        ExternalWorkflowGlobalAction,
157
        GeolocateWorkflowStatusItem,
158
        JumpWorkflowStatusItem,
159
        SendNotificationWorkflowStatusItem,
160
        RedirectToUrlWorkflowStatusItem,
161
    ):
162
        action = klass()
163
        action.parent = st0
164
        st0.items.append(action)
165

  
166
    workflow.store()
167

  
168
    data_source = NamedDataSource(name='ds_python')
169
    data_source.data_source = {'type': 'formula', 'value': repr([('1', 'un'), ('2', 'deux')])}
170
    data_source.store()
171
    data_source = NamedDataSource(name='ds_jsonp')
172
    data_source.data_source = {'type': 'jsonp', 'value': 'xxx'}
173
    data_source.store()
174

  
175
    NamedWsCall.wipe()
176
    wscall = NamedWsCall()
177
    wscall.name = 'Hello'
178
    wscall.request = {'url': 'http://example.net', 'qs_data': {'a': '=1+2'}}
179
    wscall.store()
180

  
181
    app = login(get_app(pub))
182
    resp = app.get('/backoffice/studio/deprecations/', status=302)
183
    resp = resp.follow()
184
    resp = resp.click('Go to deprecation report')
185

  
186
    assert [x.text for x in resp.pyquery('.section--ezt li a')] == [
187
        'foobar / Field "ezt_prefill"',
188
        'foobar / Field "ezt_in_datasource"',
189
        'foobar / Field "[ezt] in label"',
190
        'test / Alert',
191
        'test / Manual Jump',
192
    ]
193
    assert [x.text for x in resp.pyquery('.section--jsonp li a')] == [
194
        'foobar / Field "jsonp_data"',
195
        'Data source "ds_jsonp"',
196
    ]
197
    assert [x.text for x in resp.pyquery('.section--python-data-source li a')] == ['Data source "ds_python"']
198
    assert [x.text for x in resp.pyquery('.section--python-condition li a')] == [
199
        'foobar / Field "page1"',
200
        'foobar / Field "page2"',
201
    ]
202
    assert [x.text for x in resp.pyquery('.section--python-prefill li a')] == [
203
        'foobar / Field "python_prefill"'
204
    ]
205
    assert [x.text for x in resp.pyquery('.section--python-expression li a')] == [
206
        'test / Webservice',
207
        'test / Backoffice Data',
208
        'test / New Form Creation',
209
        'test / User Profile Update',
210
        'test / Email',
211
        'Webservice "Hello"',
212
    ]
213
    assert [x.text for x in resp.pyquery('.section--script li a')] == [
214
        'foobar / Field "{{script.usage}} in template"'
215
    ]
216

  
217

  
218
def test_deprecations_cronjob(pub):
219
    assert not os.path.exists(os.path.join(pub.app_dir, 'deprecations.json'))
220
    pub.update_deprecations_report()
221
    assert os.path.exists(os.path.join(pub.app_dir, 'deprecations.json'))
wcs/backoffice/deprecations.py
1
# w.c.s. - web application for online forms
2
# Copyright (C) 2005-2022  Entr'ouvert
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, see <http://www.gnu.org/licenses/>.
16

  
17
import datetime
18
import json
19
import os
20
import re
21

  
22
from quixote import get_publisher, get_request, get_response, redirect
23
from quixote.directory import Directory
24

  
25
from wcs.data_sources import NamedDataSource
26
from wcs.formdef import get_formdefs_of_all_kinds
27
from wcs.qommon import _, template
28
from wcs.qommon.afterjobs import AfterJob
29
from wcs.qommon.backoffice.menu import html_top
30
from wcs.workflows import Workflow
31
from wcs.wscalls import NamedWsCall
32

  
33

  
34
class DeprecationsDirectory(Directory):
35
    do_not_call_in_templates = True
36
    _q_exports = ['', 'scan']
37

  
38
    def _q_index(self):
39
        report_path = os.path.join(get_publisher().app_dir, 'deprecations.json')
40
        if not os.path.exists(report_path):
41
            # create report if necessary
42
            return self.scan()
43

  
44
        html_top('studio', _('Deprecations Report'))
45
        get_response().breadcrumb.append(('deprecations/', _('Deprecations Report')))
46

  
47
        context = {'has_sidebar': False, 'view': self}
48
        with open(report_path) as fd:
49
            context['report'] = json.load(fd)
50
        context['report']['report_lines'].sort(key=lambda x: x['category'])
51
        return template.QommonTemplateResponse(
52
            templates=['wcs/backoffice/deprecations.html'], context=context, is_django_native=True
53
        )
54

  
55
    def scan(self):
56
        job = get_response().add_after_job(
57
            DeprecationsScanAfterJob(
58
                label=_('Scanning for deprecations'),
59
                user_id=get_request().user.id,
60
                return_url='/backoffice/studio/deprecations/',
61
            )
62
        )
63
        job.store()
64
        return redirect(job.get_processing_url())
65

  
66
    @property
67
    def titles(self):
68
        return {
69
            'ezt': _('EZT text'),
70
            'jsonp': _('JSONP data source'),
71
            'python-condition': _('Python condition'),
72
            'python-expression': _('Python expression'),
73
            'python-prefill': _('Python prefill'),
74
            'python-data-source': _('Python data source'),
75
            'script': _('Filesystem Script'),
76
        }
77

  
78
    @property
79
    def short_docs(self):
80
        return {
81
            'ezt': _('Use Django templates.'),
82
            'jsonp': _('Use JSON sources with id and query parameters.'),
83
            'python-condition': _('Use Django condition.'),
84
            'python-expression': _('Use Django templates.'),
85
            'python-prefill': _('Use Django templates.'),
86
            'python-data-source': _('Use cards.'),
87
            'script': _('Use a dedicated template tags application.'),
88
        }
89

  
90
    @property
91
    def help_urls(self):
92
        return {
93
            'ezt': 'https://doc-publik.entrouvert.com/admin-fonctionnel/elements-deprecies/',
94
            'jsonp': 'https://doc-publik.entrouvert.com/admin-fonctionnel/elements-deprecies/',
95
            'python-condition': 'https://doc-publik.entrouvert.com/admin-fonctionnel/elements-deprecies/',
96
            'python-expression': 'https://doc-publik.entrouvert.com/admin-fonctionnel/elements-deprecies/',
97
            'python-prefill': 'https://doc-publik.entrouvert.com/admin-fonctionnel/elements-deprecies/',
98
            'python-data-source': 'https://doc-publik.entrouvert.com/admin-fonctionnel/elements-deprecies/',
99
            'script': 'https://doc-publik.entrouvert.com/admin-fonctionnel/elements-deprecies/',
100
        }
101

  
102

  
103
class DeprecationsScanAfterJob(AfterJob):
104
    def done_action_url(self):
105
        return self.kwargs['return_url']
106

  
107
    def done_action_label(self):
108
        return _('Go to deprecation report')
109

  
110
    def done_button_attributes(self):
111
        return {'data-redirect-auto': 'true'}
112

  
113
    def execute(self):
114
        self.report_lines = []
115
        formdefs = get_formdefs_of_all_kinds()
116
        workflows = Workflow.select(ignore_errors=True, ignore_migration=True)
117
        named_data_sources = NamedDataSource.select(ignore_errors=True, ignore_migration=True)
118
        named_ws_calls = NamedWsCall.select(ignore_errors=True, ignore_migration=True)
119
        # extra step to build report file
120
        self.total_count = len(formdefs) + len(workflows) + len(named_data_sources) + len(named_ws_calls) + 1
121
        self.store()
122

  
123
        for formdef in formdefs:
124
            for field in formdef.fields or []:
125
                location_label = _('%s / Field "%s"') % (formdef.name, field.ellipsized_label)
126
                url = formdef.get_field_admin_url(field)
127
                self.check_data_source(
128
                    getattr(field, 'data_source', None), location_label=location_label, url=url
129
                )
130
                prefill = getattr(field, 'prefill', None)
131
                if prefill:
132
                    if prefill.get('type') == 'formula':
133
                        self.add_report_line(
134
                            location_label=location_label,
135
                            url=url,
136
                            category='python-prefill',
137
                        )
138
                    else:
139
                        self.check_string(
140
                            prefill.get('value'), location_label=location_label, url=url, python_check=False
141
                        )
142
                if field.type == 'page':
143
                    for condition in field.get_conditions():
144
                        if condition and condition.get('type') == 'python':
145
                            self.add_report_line(
146
                                location_label=location_label,
147
                                url=url,
148
                                category='python-condition',
149
                            )
150
                            break
151
                if field.type in ('title', 'subtitle', 'comment'):
152
                    self.check_string(field.label, location_label=location_label, url=url, python_check=False)
153

  
154
            self.increment_count()
155

  
156
        for workflow in workflows:
157
            for action in workflow.get_all_items():
158
                location_label = '%s / %s' % (workflow.name, action.description)
159
                url = action.get_admin_url()
160
                for string in action.get_computed_strings():
161
                    self.check_string(string, location_label=location_label, url=url)
162
            self.increment_count()
163

  
164
        for named_data_source in named_data_sources:
165
            location_label = _('%s "%s"') % (_('Data source'), named_data_source.name)
166
            url = named_data_source.get_admin_url()
167

  
168
            self.check_data_source(
169
                getattr(named_data_source, 'data_source', None), location_label=location_label, url=url
170
            )
171
            self.increment_count()
172

  
173
        for named_ws_call in named_ws_calls:
174
            location_label = _('%s "%s"') % (_('Webservice'), named_ws_call.name)
175
            url = named_ws_call.get_admin_url()
176
            for string in named_ws_call.get_computed_strings():
177
                self.check_string(string, location_label=location_label, url=url)
178
            self.increment_count()
179

  
180
        self.build_report_file()
181
        self.increment_count()
182

  
183
    def check_data_source(self, data_source, location_label, url):
184
        if not data_source:
185
            return
186
        if data_source.get('type') == 'jsonp':
187
            self.add_report_line(
188
                location_label=location_label,
189
                url=url,
190
                category='jsonp',
191
            )
192
        if data_source.get('type') == 'formula':
193
            self.add_report_line(
194
                location_label=location_label,
195
                url=url,
196
                category='python-data-source',
197
            )
198
        if data_source.get('type') == 'json':
199
            self.check_string(data_source.get('value'), location_label, url, python_check=False)
200

  
201
    def check_string(self, string, location_label, url, python_check=True):
202
        if not isinstance(string, str):
203
            return
204
        if python_check and string.startswith('='):  # python expression
205
            self.add_report_line(location_label=location_label, url=url, category='python-expression')
206
        else:
207
            if template.Template(string).format == 'ezt':
208
                self.add_report_line(location_label=location_label, url=url, category='ezt')
209
            if re.findall(r'\Wscript\.\w', string):
210
                self.add_report_line(location_label=location_label, url=url, category='script')
211

  
212
    def add_report_line(self, **kwargs):
213
        if kwargs not in self.report_lines:
214
            self.report_lines.append(kwargs)
215

  
216
    def build_report_file(self):
217
        with open(os.path.join(get_publisher().app_dir, 'deprecations.json'), 'w') as fd:
218
            json.dump(
219
                {
220
                    'now': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
221
                    'report_lines': self.report_lines,
222
                },
223
                fd,
224
                indent=2,
225
            )
wcs/backoffice/studio.py
18 18
from quixote.directory import Directory
19 19

  
20 20
from wcs.admin.logged_errors import LoggedErrorsDirectory
21
from wcs.backoffice.deprecations import DeprecationsDirectory
21 22
from wcs.blocks import BlockDef
22 23
from wcs.carddef import CardDef
23 24
from wcs.data_sources import NamedDataSource
......
31 32

  
32 33

  
33 34
class StudioDirectory(Directory):
34
    _q_exports = ['', ('logged-errors', 'logged_errors_dir')]
35
    _q_exports = ['', 'deprecations', ('logged-errors', 'logged_errors_dir')]
36

  
37
    deprecations = DeprecationsDirectory()
35 38

  
36 39
    def __init__(self):
37 40
        self.logged_errors_dir = LoggedErrorsDirectory(parent_dir=self)
......
67 70
            object_types += [NamedWsCall]
68 71
        if backoffice_root.is_accessible('cards'):
69 72
            object_types += [CardDef]
73

  
70 74
        context = {
71 75
            'has_sidebar': False,
72 76
            'extra_links': extra_links,
wcs/blocks.py
101 101
        base_url = get_publisher().get_backoffice_url()
102 102
        return '%s/forms/blocks/%s/' % (base_url, self.id)
103 103

  
104
    def get_field_admin_url(self, field):
105
        return self.get_admin_url() + '%s/' % field.id
106

  
104 107
    def get_display_value(self, value):
105 108
        if not self.digest_template:
106 109
            return self.name
wcs/data_sources.py
591 591

  
592 592
    def get_admin_url(self):
593 593
        base_url = get_publisher().get_backoffice_url()
594
        for section in ('settings', 'forms', 'workflows'):
595
            if get_publisher().get_backoffice_root().is_accessible(section):
596
                return '%s/%s/data-sources/%s/' % (base_url, section, self.id)
594
        if get_request():
595
            for section in ('settings', 'forms', 'workflows'):
596
                if get_publisher().get_backoffice_root().is_accessible(section):
597
                    return '%s/%s/data-sources/%s/' % (base_url, section, self.id)
597 598
        # fallback to settings section
598 599
        section = 'settings'
599 600
        return '%s/%s/data-sources/%s/' % (base_url, section, self.id)
wcs/fields.py
2734 2734
    def add_to_view_form(self, *args, **kwargs):
2735 2735
        pass
2736 2736

  
2737
    def get_conditions(self):
2738
        if self.condition:
2739
            yield self.condition
2740
        for post_condition in self.post_conditions or []:
2741
            yield post_condition.get('condition')
2742

  
2737 2743

  
2738 2744
register_field_class(PageField)
2739 2745

  
wcs/formdef.py
773 773
        base_url = get_publisher().get_backoffice_url()
774 774
        return '%s/forms/%s/' % (base_url, self.id)
775 775

  
776
    def get_field_admin_url(self, field):
777
        return self.get_admin_url() + 'fields/%s/' % field.id
778

  
776 779
    def get_backoffice_submission_url(self):
777 780
        base_url = get_publisher().get_backoffice_url() + '/submission'
778 781
        return '%s/%s/' % (base_url, self.url_name)
wcs/publisher.py
118 118
        cls.register_cronjob(
119 119
            CronJob(cls.apply_global_action_timeouts, name='evaluate_global_action_timeouts', minutes=[0])
120 120
        )
121
        # once a day : update deprecations report
122
        cls.register_cronjob(
123
            CronJob(cls.update_deprecations_report, name='update_deprecations_report', hours=[2], minutes=[0])
124
        )
125
        # other jobs
121 126
        data_sources.register_cronjob()
122 127
        formdef.register_cronjobs()
123 128

  
129
    def update_deprecations_report(self):
130
        from .backoffice.deprecations import DeprecationsScanAfterJob
131

  
132
        DeprecationsScanAfterJob().execute()
133

  
124 134
    def is_using_postgresql(self):
125 135
        return bool(self.has_site_option('postgresql') and self.cfg.get('postgresql', {}))
126 136

  
wcs/qommon/static/css/dc2/admin.scss
1972 1972
	}
1973 1973
}
1974 1974

  
1975
.errors-and-deprecations {
1976
	display: flex;
1977
	flex-direction: column;
1978
	.recent-errors {
1979
		flex: 1;
1980
		margin-bottom: 0;
1981
	}
1982
}
1983

  
1984
.no-deprecations {
1985
	min-height: 80vh;
1986
	background: transparent url(/static/css/dc2/fireworks.svg) bottom 20% left 50% no-repeat;
1987
	background-size: contain;
1988
}
1989

  
1990
.deprecations {
1991
	.pk-information {
1992
		margin-top: 0;
1993
	}
1994
	ul {
1995
		column-width: 40em;
1996
	}
1997
}
1998

  
1975 1999
#main-content form#multi-actions {
1976 2000
	padding: 0;
1977 2001
}
wcs/qommon/template.py
476 476
        self.raises = raises
477 477

  
478 478
        if ('{{' in value or '{%' in value) and not ezt_only:  # Django template
479
            self.format = 'django'
479 480
            self.render = self.django_render
480 481
            if autoescape is False:
481 482
                value = '{%% autoescape off %%}%s{%% endautoescape %%}' % value
......
489 490
                self.render = self.null_render
490 491

  
491 492
        elif '[' in value:  # ezt template
493
            self.format = 'ezt'
492 494
            self.render = self.ezt_render
493 495
            self.template = ezt.Template(compress_whitespace=False)
494 496
            try:
......
499 501
                self.render = self.null_render
500 502

  
501 503
        else:
504
            self.format = 'plain'
502 505
            self.render = self.null_render
503 506

  
504 507
    def null_render(self, context=None):
wcs/templates/wcs/backoffice/deprecations.html
1
{% extends "wcs/backoffice.html" %}
2
{% load i18n %}
3

  
4
{% block content %}
5

  
6
<div id="appbar">
7
<h2>{% trans "Deprecations Report" %}</h2>
8
<span class="actions">
9
<a href="scan">{% trans "Rescan for deprecations" %}</a>
10
</span>
11
</div>
12

  
13
{% if not report.report_lines %}
14
<div class="no-deprecations">
15
        <p>{% trans "No deprecated items were found on this site." %}</p>
16
</div>
17

  
18
{% else %}
19
<div class="section deprecations">
20

  
21
{% regroup report.report_lines by category as category_report_lines %}
22
{% for category in category_report_lines %}
23
<h3>{{ view.titles|get:category.grouper }}</h3>
24
<div class="section--{{ category.grouper }}">
25
<div class="pk-information">
26
  <p>{{ view.short_docs|get:category.grouper }}
27
  <a href="{{ view.help_urls|get:category.grouper }}">{% trans "Read more" %}</a></p>
28
</div>
29
<ul>
30
 {% for report_line in category.list %}
31
 <li><a href="{{ report_line.url }}">{{ report_line.location_label }}</a></li>
32
 {% endfor %}
33
</ul>
34
</div>
35
{% endfor %}
36
</div>
37

  
38
<p>{% trans "Generated on:" %} {{ report.now|datetime }}</p>
39
{% endif %}
40

  
41
{% endblock %}
wcs/templates/wcs/backoffice/studio.html
42 42
      </ul>
43 43
    </div>
44 44

  
45
    <div class="paragraph {% if not recent_errors %}no-recent-errors{% endif %}">
45
    <div class="errors-and-deprecations">
46
    <div class="paragraph recent-errors {% if not recent_errors %}no-recent-errors{% endif %}">
46 47
      <h3>{% trans "Recent errors" context "studio" %}</h3>
47 48
      {% if recent_errors %}
48 49
        <ul>
......
55 56
        <p>{% trans "No errors, congratulations!" %}</p>
56 57
      {% endif %}
57 58
    </div>
59
    <div>
60
      <p><a class="button button-paragraph" href="deprecations/">{% trans "Deprecations Report" %}</a></p>
61
    </div>
62
    </div>
58 63
  </div>
59 64
</div>
60 65
{% endblock %}
wcs/wf/backoffice_fields.py
129 129
                result.append(htmltext('<li>#%s → %s</li>') % (field['field_id'], field['value']))
130 130
        return htmltext('<ul class="fields">%s</ul>') % htmltext('').join(result)
131 131

  
132
    def get_computed_strings(self):
133
        yield from super().get_computed_strings()
134
        for field in self.fields or []:
135
            yield field.get('value')
136

  
132 137
    def perform(self, formdata):
133 138
        if not self.fields:
134 139
            return
wcs/wf/create_formdata.py
536 536
        else:
537 537
            dest.user_id = None
538 538

  
539
    def get_computed_strings(self):
540
        yield from super().get_computed_strings()
541
        yield self.user_association_template
542
        for mapping in self.mappings or []:
543
            yield mapping.expression
544

  
539 545
    def perform(self, formdata):
540 546
        formdef = self.formdef
541 547
        if not formdef or not (self.mappings or self.map_fields_by_varname):
wcs/wf/dispatch.py
249 249
            return users_by_name[0].id
250 250
        return None
251 251

  
252
    def get_computed_strings(self):
253
        yield from super().get_computed_strings()
254
        yield from [self.role_id, self.variable]
255

  
252 256
    def perform(self, formdata):
253 257
        if not formdata.workflow_roles:
254 258
            formdata.workflow_roles = {}
wcs/wf/export_to_model.py
762 762
        upload.fp.seek(0)
763 763
        self.model_file = UploadedFile('models', filename, upload)
764 764

  
765
    def get_computed_strings(self):
766
        yield from super().get_computed_strings()
767
        yield self.filename
768

  
765 769
    def perform(self, formdata):
766 770
        if self.method == 'interactive':
767 771
            return
wcs/wf/external_workflow.py
259 259
    def get_parameters(self):
260 260
        return ('slug', 'trigger_id', 'target_mode', 'target_id', 'condition')
261 261

  
262
    def get_computed_strings(self):
263
        yield from super().get_computed_strings()
264
        yield self.target_id
265

  
262 266
    def perform(self, formdata):
263 267
        objectdef = self.get_object_def()
264 268
        if not objectdef:
wcs/wf/geolocate.py
115 115
                value=self.overwrite,
116 116
            )
117 117

  
118
    def get_computed_strings(self):
119
        yield from super().get_computed_strings()
120
        yield from [self.address_string, self.map_variable, self.photo_variable]
121

  
118 122
    def perform(self, formdata):
119 123
        if not self.method:
120 124
            return
wcs/wf/jump.py
247 247
        except ValueError:
248 248
            return None
249 249

  
250
    def get_computed_strings(self):
251
        yield from super().get_computed_strings()
252
        yield self.timeout
253

  
250 254
    def perform(self, formdata):
251 255
        wf_status = self.get_target_status(formdata)
252 256
        if wf_status:
wcs/wf/notification.py
138 138
                self.to = [self.to]
139 139
                self.users_template = None
140 140

  
141
    def get_computed_strings(self):
142
        yield from super().get_computed_strings()
143
        yield from [self.title, self.body, self.users_template]
144

  
141 145
    def perform(self, formdata):
142 146
        if not (self.is_available() and (self.to or self.users_template) and self.title and self.body):
143 147
            return
wcs/wf/profile.py
138 138
        if fields:
139 139
            self.fields = fields
140 140

  
141
    def get_computed_strings(self):
142
        yield from super().get_computed_strings()
143
        for field in self.fields or []:
144
            yield field.get('value')
145

  
141 146
    def perform(self, formdata):
142 147
        if not self.fields:
143 148
            return
wcs/wf/redirect_to_url.py
50 50
            )
51 51
            widget.extra_css_class = 'grid-1-1'
52 52

  
53
    def get_computed_strings(self):
54
        yield from super().get_computed_strings()
55
        yield self.url
56

  
53 57
    def perform(self, formdata):
54 58
        if not self.url:
55 59
            # action not yet configured: don't redirect
wcs/wf/sendmail.py
210 210
    def get_body_parameter_view_value(self):
211 211
        return htmltext('<pre class="wrapping-pre">%s</pre>') % self.body
212 212

  
213
    def get_computed_strings(self):
214
        yield from super().get_computed_strings()
215
        if not self.mail_template:
216
            yield self.subject
217
            yield self.body
218
        if self.to:
219
            yield from self.to
220

  
213 221
    def perform(self, formdata):
214 222
        if not self.to:
215 223
            return
wcs/wf/wscall.py
386 386
    def get_qs_data_parameter_view_value(self):
387 387
        return self._get_dict_parameter_view_value(self.qs_data)
388 388

  
389
    def get_computed_strings(self):
390
        yield from super().get_computed_strings()
391
        yield self.url
392
        if self.qs_data:
393
            yield from self.qs_data.values()
394
        if self.post_data:
395
            yield from self.post_data.values()
396

  
389 397
    def perform(self, formdata):
390 398
        if not self.url:
391 399
            # misconfigured action
wcs/workflows.py
1750 1750
            del odict['parent']
1751 1751
        return odict
1752 1752

  
1753
    def get_admin_url(self):
1754
        return '%s/global-actions/%s/' % (self.parent.get_admin_url(), self.id)
1755

  
1753 1756
    def append_item(self, type):
1754 1757
        for klass in item_classes:
1755 1758
            if klass.key == type:
......
2225 2228
    def get_line_details(self):
2226 2229
        return ''
2227 2230

  
2231
    def get_admin_url(self):
2232
        return self.parent.get_admin_url() + 'items/%s/' % self.id
2233

  
2228 2234
    def render_list_of_roles(self, roles):
2229 2235
        return self.parent.parent.render_list_of_roles(roles)
2230 2236

  
......
2315 2321
    def get_parameters(self):
2316 2322
        return ('condition',)
2317 2323

  
2324
    def get_computed_strings(self):
2325
        # get list of computed strings, to check for deprecations
2326
        return []
2327

  
2318 2328
    def get_parameters_view(self):
2319 2329
        r = TemplateIO(html=True)
2320 2330
        form = Form()
......
3008 3018
        else:
3009 3019
            return _('not completed')
3010 3020

  
3021
    def get_computed_strings(self):
3022
        yield from super().get_computed_strings()
3023
        yield self.label
3024

  
3011 3025
    def fill_form(self, form, formdata, user, **kwargs):
3012 3026
        label = self.compute(self.label)
3013 3027
        if not label:
......
3209 3223
        if 'body' in parameters:
3210 3224
            form.add(TextWidget, '%sbody' % prefix, title=_('Body'), value=self.body, cols=80, rows=10)
3211 3225

  
3226
    def get_computed_strings(self):
3227
        yield from super().get_computed_strings()
3228
        yield self.body
3229
        if self.to:
3230
            yield from self.to
3231

  
3212 3232
    def perform(self, formdata):
3213 3233
        if not self.is_available():
3214 3234
            return
......
3267 3287
            parts.append(_('for %s') % self.render_list_of_roles(self.to))
3268 3288
        return ', '.join([str(x) for x in parts])
3269 3289

  
3290
    def get_computed_strings(self):
3291
        yield from super().get_computed_strings()
3292
        yield self.message
3293

  
3270 3294
    def get_message(self, filled, position='top'):
3271 3295
        if not (self.message and self.position == position and filled.is_for_current_user(self.to)):
3272 3296
            return ''
wcs/wscalls.py
214 214
        base_url = get_publisher().get_backoffice_url()
215 215
        return '%s/settings/wscalls/%s/' % (base_url, self.slug)
216 216

  
217
    def get_computed_strings(self):
218
        if self.request:
219
            yield self.request.get('url')
220
            yield self.request.get('request_signature_key')
221
            if self.request.get('qs_data'):
222
                yield from self.request.get('qs_data').values()
223
            if self.request.get('post_data'):
224
                yield from self.request.get('post_data').values()
225

  
217 226
    def export_request_to_xml(self, element, attribute_name, charset, **kwargs):
218 227
        request = getattr(self, attribute_name)
219 228
        for attr in ('url', 'request_signature_key', 'method'):
220
-