0001-backoffice-add-a-deprecations-report-page-58799.patch
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 |
- |