0001-general-remove-legacy-theming-code-70128.patch
MANIFEST.in | ||
---|---|---|
4 | 4 |
recursive-include wcs/locale *.po *.mo |
5 | 5 |
recursive-include extra/ *.py |
6 | 6 |
recursive-include data/web/ *.html *.css *.png |
7 |
recursive-include data/themes/default/ *.html *.css *.png *.gif *.jpg *.js *.ezt *.xml |
|
8 |
recursive-include data/themes/alto/ *.html *.css *.png *.gif *.jpg *.js *.ezt *.xml |
|
9 | 7 |
recursive-include data/vendor/ *.dat |
10 | 8 |
recursive-include wcs/qommon/static/ *.css *.scss *.png *.gif *.jpg *.js *.eot *.svg *.ttf *.woff *.map |
11 | 9 |
recursive-include wcs/templates *.html *.txt |
data/themes/alto/desc.xml | ||
---|---|---|
1 |
<?xml version="1.0"?> |
|
2 |
<theme name="alto" version="1.0"> |
|
3 |
<label>Alto</label> |
|
4 |
<desc>Alto theme</desc> |
|
5 |
<author>Frederic Peters (original Dotclear theme (alto studio) by David Jubert)</author> |
|
6 |
</theme> |
data/themes/alto/template.ezt | ||
---|---|---|
1 |
<!DOCTYPE html> |
|
2 |
<html lang="[site_lang]"> |
|
3 |
<head> |
|
4 |
<title>[page_title]</title> |
|
5 |
<link rel="stylesheet" type="text/css" href="[css]"/> |
|
6 |
[script] |
|
7 |
</head> |
|
8 |
<body[if-any onload] onload="[onload]"[end]> |
|
9 |
<div id="page"> |
|
10 |
<div id="top"> <h1>[if-any title][title][else][site_name][end]</h1> </div> |
|
11 |
<div id="main-content"> |
|
12 |
[if-any breadcrumb]<p id="breadcrumb">Vous êtes ici : [breadcrumb]</p>[end] |
|
13 |
[body] |
|
14 |
</div> |
|
15 |
<div id="footer"></div> |
|
16 |
</div> |
|
17 |
</body> |
|
18 |
</html> |
data/themes/alto/wcs.css | ||
---|---|---|
1 |
/* adapted from alto dotclear theme */ |
|
2 | ||
3 |
@import url(/static/xstatic/themes/smoothness/jquery-ui.min.css); |
|
4 |
@import url(/static/css/qommon.css); |
|
5 | ||
6 |
html, body { |
|
7 |
background: #CCCCCC; |
|
8 |
font-family: sans-serif; |
|
9 |
color: #333333; |
|
10 |
margin: 0; |
|
11 |
padding: 0; |
|
12 |
text-align: center; |
|
13 |
height: 100%; |
|
14 |
margin-bottom: 1px; |
|
15 |
} |
|
16 | ||
17 |
fieldset { |
|
18 |
border: none; |
|
19 |
} |
|
20 | ||
21 |
label { |
|
22 |
cursor: pointer; |
|
23 |
cursor: hand; |
|
24 |
} |
|
25 | ||
26 |
img { |
|
27 |
border: 0; |
|
28 |
} |
|
29 | ||
30 |
input,textarea { |
|
31 |
border: 1px solid #999; |
|
32 |
} |
|
33 | ||
34 |
textarea { |
|
35 |
width: 99%; |
|
36 |
} |
|
37 | ||
38 |
a { |
|
39 |
color: #000; |
|
40 |
text-decoration : none; |
|
41 |
} |
|
42 | ||
43 |
a:hover { |
|
44 |
color: #0273B9; |
|
45 |
text-decoration : underline; |
|
46 |
} |
|
47 | ||
48 |
a:visited { |
|
49 |
color: #0273B9; |
|
50 |
text-decoration : none; |
|
51 |
} |
|
52 | ||
53 |
#page { |
|
54 |
background: #fff url(img/page.jpg) repeat-y center top; |
|
55 |
color: inherit; |
|
56 |
width: 886px; |
|
57 |
margin: 0 auto; |
|
58 |
text-align: left; |
|
59 |
padding: 0px; |
|
60 |
} |
|
61 |
|
|
62 |
#top { |
|
63 |
margin: 0; |
|
64 |
padding: 0; |
|
65 |
background: #CCCCCC url(img/top.jpg) no-repeat left top; |
|
66 |
margin-bottom: 2em; |
|
67 |
} |
|
68 | ||
69 |
#top h1 { |
|
70 |
width: 706px; |
|
71 |
margin: 0 auto; |
|
72 |
padding-top: 70px; |
|
73 |
} |
|
74 | ||
75 |
#side { |
|
76 |
float: right; |
|
77 |
width: 204px; |
|
78 |
padding: 0; |
|
79 |
margin: 0 -20px 0 20px; |
|
80 |
} |
|
81 | ||
82 |
#side #tracking-code { |
|
83 |
margin-bottom: 1em; |
|
84 |
border: 1px solid #bfbfbf; |
|
85 |
color: #333333; |
|
86 |
background: #e6e6e6; |
|
87 |
padding: 1ex; |
|
88 |
} |
|
89 | ||
90 |
#side #tracking-code h3 { |
|
91 |
margin: 0; |
|
92 |
} |
|
93 | ||
94 |
#side #tracking-code button, |
|
95 |
#side #tracking-code a { |
|
96 |
margin: 1ex auto; |
|
97 |
display: block; |
|
98 |
text-align: center; |
|
99 |
font-size: 120%; |
|
100 |
background: white; |
|
101 |
border: 1px solid black; |
|
102 |
padding: 0.5ex 0; |
|
103 |
width: 10em; |
|
104 |
} |
|
105 | ||
106 |
#side #tracking-code button { |
|
107 |
background: #0273B9; |
|
108 |
color: white; |
|
109 |
} |
|
110 | ||
111 |
input[name=savedraft] { |
|
112 |
display: none; |
|
113 |
} |
|
114 | ||
115 |
#steps { |
|
116 |
background: white; |
|
117 |
border: 1px solid #bfbfbf; |
|
118 |
color: #333333; |
|
119 |
background: #e6e6e6; |
|
120 |
-moz-border-radius: 6px; |
|
121 |
text-align: left; |
|
122 |
} |
|
123 | ||
124 | ||
125 |
#footer { |
|
126 |
width: 886px; |
|
127 |
height: 123px; |
|
128 |
background: #CCCCCC url(img/bottom.jpg) no-repeat left top; |
|
129 |
margin: 0; |
|
130 |
margin-top: 1em; |
|
131 |
color: #666; |
|
132 |
clear: both; |
|
133 |
} |
|
134 | ||
135 |
#footer p { |
|
136 |
width: 706px; |
|
137 |
margin: 0 auto; |
|
138 |
padding-top: 24px; |
|
139 |
text-align: right; |
|
140 |
font-size: 80%; |
|
141 |
} |
|
142 | ||
143 | ||
144 |
#main-content { |
|
145 |
width: 735px; |
|
146 |
padding-left: 65px; |
|
147 |
text-align: justify; |
|
148 |
} |
|
149 | ||
150 | ||
151 |
div#steps ol { |
|
152 |
list-style: none; |
|
153 |
margin: 0; |
|
154 |
padding: 0.5em; |
|
155 |
} |
|
156 | ||
157 |
div#steps li { |
|
158 |
display: block; |
|
159 |
border: 1px solid #ddd; |
|
160 |
margin: 0.5em 0; |
|
161 |
background: #eee; |
|
162 |
color: #aaa; |
|
163 |
} |
|
164 | ||
165 |
#steps span.marker { |
|
166 |
padding: 0 1ex 0 1ex; |
|
167 |
font-weight: bold; |
|
168 |
color: white; |
|
169 |
text-align: center; |
|
170 |
background: #ddd; |
|
171 |
} |
|
172 | ||
173 |
#steps li.current span.marker { |
|
174 |
background: #0273b9; |
|
175 |
} |
|
176 | ||
177 | ||
178 |
#steps li.current { |
|
179 |
font-weight: bold; |
|
180 |
border: 1px solid #333333; |
|
181 |
} |
|
182 | ||
183 |
#steps li.current span.label { |
|
184 |
color: #333333; |
|
185 |
} |
|
186 | ||
187 |
#steps ol ul { |
|
188 |
margin-right: 1em; |
|
189 |
font-size: 90%; |
|
190 |
} |
|
191 | ||
192 |
#steps ol ul li { |
|
193 |
padding: 0 2px; |
|
194 |
font-weight: normal; |
|
195 |
margin-left: -1ex; |
|
196 |
} |
|
197 | ||
198 |
#steps ol ul li.current { |
|
199 |
border-color: inherit; |
|
200 |
color: #333333; |
|
201 |
} |
|
202 | ||
203 | ||
204 |
div.widget { |
|
205 |
clear: none; |
|
206 |
margin-bottom: 1.5em; |
|
207 |
} |
|
208 | ||
209 |
hr { |
|
210 |
visibility: hidden; |
|
211 |
} |
|
212 | ||
213 |
textarea { |
|
214 |
} |
|
215 | ||
216 |
p#breadcrumb { |
|
217 |
background: #e6e6e6; |
|
218 |
-moz-border-radius: 6px; |
|
219 |
width: 750px; |
|
220 |
padding: 3px; |
|
221 |
font-size: 90%; |
|
222 |
border: 1px solid #bfbfbf; |
|
223 |
} |
|
224 | ||
225 |
div#receipt { |
|
226 |
} |
|
227 | ||
228 |
div#receipt span.label { |
|
229 |
font-weight: bold; |
|
230 |
display: block; |
|
231 |
} |
|
232 | ||
233 |
div#receipt span.value { |
|
234 |
display: block; |
|
235 |
margin-left: 1em; |
|
236 |
} |
|
237 | ||
238 |
form div.page, |
|
239 |
div#receipt div.page { |
|
240 |
border: 1px solid #bfbfbf; |
|
241 |
padding: 1ex; |
|
242 |
margin-bottom: 1em; |
|
243 |
} |
|
244 | ||
245 |
form div.page p, |
|
246 |
div#receipt div.page p { |
|
247 |
margin-top: 0; |
|
248 |
} |
|
249 | ||
250 |
form div.page h3, |
|
251 |
div#receipt div.page h3 { |
|
252 |
margin: 0; |
|
253 |
margin-bottom: 1ex; |
|
254 |
} |
|
255 | ||
256 | ||
257 |
p#receiver { |
|
258 |
margin: 0; |
|
259 |
margin-left: 2em; |
|
260 |
margin-top: -0.7em; |
|
261 |
margin-bottom: 1em; |
|
262 |
padding: 2px 5px; |
|
263 |
font-weight: bold; |
|
264 |
} |
|
265 | ||
266 |
table#listing { |
|
267 |
background: white; |
|
268 |
border: 1px solid #888; |
|
269 |
} |
|
270 |
data/themes/default/desc.xml | ||
---|---|---|
1 |
<?xml version="1.0"?> |
|
2 |
<theme name="default" version="1.0"> |
|
3 |
<label>Default</label> |
|
4 |
<desc>Default theme</desc> |
|
5 |
<author>Frederic Peters & Dotclear Team</author> |
|
6 |
</theme> |
data/themes/django/templates/wcs/base.html | ||
---|---|---|
12 | 12 |
<div id="page"> |
13 | 13 |
<div id="top"> |
14 | 14 |
{% block header %} |
15 |
<h1>WIP/DJANGO - {% if title %}{{ title }}{% else %}{{ site_name }}{% endif %}</h1>
|
|
15 |
<h1>{% if title %}{{ title }}{% else %}{{ site_name }}{% endif %}</h1> |
|
16 | 16 |
{% endblock %} |
17 | 17 |
</div> |
18 | 18 |
<div id="main-content"> |
tests/admin_pages/test_settings.py | ||
---|---|---|
28 | 28 |
from wcs.formdef import FormDef |
29 | 29 |
from wcs.qommon.form import UploadedFile |
30 | 30 |
from wcs.qommon.http_request import HTTPRequest |
31 |
from wcs.qommon.template import get_current_theme |
|
32 | 31 |
from wcs.wf.export_to_model import ExportToModel |
33 | 32 |
from wcs.workflows import Workflow |
34 | 33 |
from wcs.wscalls import NamedWsCall |
... | ... | |
74 | 73 |
app = login(get_app(pub)) |
75 | 74 |
resp = app.get('/backoffice/settings/') |
76 | 75 |
assert 'Identification' in resp.text |
77 |
assert 'Theme' in resp.text |
|
78 | 76 | |
79 | 77 |
if not pub.site_options.has_section('options'): |
80 | 78 |
pub.site_options.add_section('options') |
81 |
pub.site_options.set('options', 'settings-disabled-screens', 'identification, theme')
|
|
79 |
pub.site_options.set('options', 'settings-disabled-screens', 'identification') |
|
82 | 80 |
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: |
83 | 81 |
pub.site_options.write(fd) |
84 | 82 |
resp = app.get('/backoffice/settings/') |
85 | 83 |
assert 'Identification' not in resp.text |
86 |
assert 'Theme' not in resp.text |
|
87 | 84 | |
88 | 85 | |
89 | 86 |
def test_settings_export_import(pub): |
... | ... | |
318 | 315 |
assert 'Unknown referenced objects [Unknown fields blocks: unknown]' in resp |
319 | 316 | |
320 | 317 | |
321 |
def test_settings_themes(pub): |
|
322 |
create_superuser(pub) |
|
323 |
app = login(get_app(pub)) |
|
324 | ||
325 |
# create mock theme |
|
326 |
os.mkdir(os.path.join(pub.app_dir, 'themes')) |
|
327 |
os.mkdir(os.path.join(pub.app_dir, 'themes', 'test')) |
|
328 |
with open(os.path.join(pub.app_dir, 'themes', 'test', 'desc.xml'), 'w') as fd: |
|
329 |
fd.write( |
|
330 |
'<?xml version="1.0"?>' |
|
331 |
'<theme name="test" version="1.0">' |
|
332 |
' <label>Test Theme</label>' |
|
333 |
'</theme>' |
|
334 |
) |
|
335 | ||
336 |
resp = app.get('/backoffice/settings/themes') |
|
337 |
assert 'biglist themes' in resp.text |
|
338 |
assert 'Test Theme (1.0)' in resp.text |
|
339 | ||
340 |
# just for the kick, there's no support for uploading file in webtest 1.3 |
|
341 |
resp = app.get('/backoffice/settings/themes') |
|
342 |
resp.click('Install New Theme') |
|
343 | ||
344 |
# select the theme |
|
345 |
resp = app.get('/backoffice/settings/themes') |
|
346 |
resp.forms[0]['theme'].value = 'test' |
|
347 |
resp = resp.forms[0].submit() |
|
348 |
assert resp.location == 'http://example.net/backoffice/settings/' |
|
349 | ||
350 |
resp = app.get('/backoffice/settings/themes') |
|
351 |
assert 'checked' in resp.text |
|
352 |
assert get_current_theme()['name'] == 'test' |
|
353 | ||
354 | ||
355 |
def test_settings_template(pub): |
|
356 |
create_superuser(pub) |
|
357 |
app = login(get_app(pub)) |
|
358 |
resp = app.get('/backoffice/settings/template') |
|
359 | ||
360 |
# change template |
|
361 |
orig_value = resp.forms[0]['template'].value |
|
362 |
assert 'foobar' not in orig_value |
|
363 |
resp.forms[0]['template'] = orig_value + '<!-- foobar -->' |
|
364 |
resp = resp.forms[0].submit('submit') |
|
365 | ||
366 |
# restore default template |
|
367 |
resp = app.get('/backoffice/settings/template') |
|
368 |
assert 'foobar' in resp.forms[0]['template'].value |
|
369 |
resp = resp.forms[0].submit('restore-default') |
|
370 | ||
371 |
# check |
|
372 |
resp = app.get('/backoffice/settings/template') |
|
373 |
assert resp.forms[0]['template'].value == orig_value |
|
374 | ||
375 | ||
376 | 318 |
def test_settings_user(pub): |
377 | 319 |
user = create_superuser(pub) |
378 | 320 |
app = login(get_app(pub)) |
... | ... | |
829 | 771 |
assert pub.cfg['admin-permissions']['workflows'] == [] |
830 | 772 | |
831 | 773 | |
832 |
def test_settings_theme_preview(pub): |
|
833 |
create_superuser(pub) |
|
834 | ||
835 |
FormDef.wipe() |
|
836 |
formdef = FormDef() |
|
837 |
formdef.name = 'form title' |
|
838 |
formdef.fields = [] |
|
839 |
formdef.store() |
|
840 | ||
841 |
app = login(get_app(pub)) |
|
842 |
assert 'alto/wcs.css' not in app.get('/').text |
|
843 |
resp = app.get('/backoffice/settings/themes') |
|
844 |
assert resp.form['theme'].value in ('default', 'django') |
|
845 | ||
846 |
# visit theme preview |
|
847 |
resp = resp.click(href='theme_preview/alto/') |
|
848 |
assert 'alto/wcs.css' in resp.text |
|
849 | ||
850 |
# get into a form, making sure we are kept in theme preview |
|
851 |
resp = resp.click('form title') |
|
852 |
assert 'alto/wcs.css' in resp.text |
|
853 | ||
854 |
# verify submits are not allowed |
|
855 |
resp = resp.form.submit('submit') |
|
856 |
assert "The theme preview doesn't support this." in resp.text |
|
857 | ||
858 | ||
859 |
def test_settings_theme_download_upload(pub): |
|
860 |
create_superuser(pub) |
|
861 | ||
862 |
# download existing theme |
|
863 |
app = login(get_app(pub)) |
|
864 |
resp = app.get('/backoffice/settings/themes') |
|
865 |
resp = resp.click('download', index=0) |
|
866 |
assert resp.headers['content-type'] == 'application/zip' |
|
867 | ||
868 |
zip_content = io.BytesIO(resp.body) |
|
869 |
with zipfile.ZipFile(zip_content, 'a') as zipf: |
|
870 |
filelist = zipf.namelist() |
|
871 |
assert 'alto/icon.png' in filelist |
|
872 |
assert 'alto/desc.xml' in filelist |
|
873 |
assert 'alto/template.ezt' in filelist |
|
874 |
assert 'alto/wcs.css' in filelist |
|
875 | ||
876 |
# modify it |
|
877 |
zipf.writestr('alto/foobar.txt', 'XXX') |
|
878 | ||
879 |
# upload it |
|
880 |
resp = app.get('/backoffice/settings/themes') |
|
881 |
resp = resp.click('Install New Theme') |
|
882 |
resp.form['file'] = Upload('alto-modified.zip', zip_content.getvalue()) |
|
883 |
resp = resp.form.submit() |
|
884 |
assert os.path.exists(os.path.join(pub.app_dir, 'themes/alto/foobar.txt')) |
|
885 | ||
886 |
assert app.get('/themes/alto/foobar.txt').text == 'XXX' |
|
887 |
assert 'Directory listing denied' in app.get('/themes/alto/', status=200).text |
|
888 | ||
889 |
assert app.get('/themes/alto/plop', status=404) |
|
890 |
assert app.get('/themes/alto/../', status=404) |
|
891 |
assert app.get('/themes/xxx/../', status=404) |
|
892 | ||
893 | ||
894 | 774 |
def test_postgresql_settings(pub): |
895 | 775 |
create_superuser(pub) |
896 | 776 |
tests/form_pages/test_all.py | ||
---|---|---|
343 | 343 |
assert resp.location == 'http://www.example.com/en/foobar/' |
344 | 344 | |
345 | 345 | |
346 |
def test_legacy_theme_misc(): |
|
347 |
pub = create_temporary_pub(legacy_theme_mode=True) |
|
348 |
pub.cfg['language'] = {'language': 'en'} |
|
349 |
pub.write_cfg() |
|
350 | ||
351 |
formdef = create_formdef() |
|
352 |
formdef.fields = [fields.StringField(id='1', label='string')] |
|
353 |
formdef.store() |
|
354 | ||
355 |
resp = get_app(pub).get('/') |
|
356 |
assert '<title>' in resp.text |
|
357 |
assert '/static/js/qommon.forms.js' not in resp.text |
|
358 |
assert '<a class="" href="test/">test</a>' in resp.text |
|
359 |
resp = resp.click('test') |
|
360 |
assert '/static/js/qommon.forms.js' in resp.text |
|
361 |
resp.form['f1'] = 'TEST' |
|
362 |
resp = resp.form.submit('submit') |
|
363 |
assert 'Check values then click submit.' in resp.text |
|
364 |
resp = resp.form.submit('submit').follow() |
|
365 |
assert 'The form has been recorded on' in resp.text |
|
366 | ||
367 | ||
368 | 346 |
def test_form_access(pub): |
369 | 347 |
formdef = create_formdef() |
370 | 348 |
get_app(pub).get('/test/', status=200) |
tests/test_hobo.py | ||
---|---|---|
314 | 314 | |
315 | 315 |
def test_update_themes(setuptest): |
316 | 316 |
pub, hobo_cmd = setuptest |
317 |
pub.cfg['branding'] = {'theme': 'default'}
|
|
317 |
pub.cfg['branding'] = {'theme': 'django'}
|
|
318 | 318 |
service = [x for x in HOBO_JSON.get('services', []) if x.get('service-id') == 'wcs'][0] |
319 | 319 |
hobo_cmd.update_configuration(service, pub) |
320 |
assert pub.cfg['branding']['theme'] == 'default'
|
|
320 |
assert pub.cfg['branding']['theme'] == 'django'
|
|
321 | 321 | |
322 | 322 |
service['variables']['theme'] = 'foobar' |
323 | 323 |
hobo_cmd.update_configuration(service, pub) |
324 |
assert pub.cfg['branding']['theme'] == 'default'
|
|
324 |
assert pub.cfg['branding']['theme'] == 'django'
|
|
325 | 325 | |
326 | 326 |
hobo_cmd.THEMES_DIRECTORY = os.path.join(os.path.dirname(__file__), 'themes') |
327 | 327 |
hobo_cmd.update_configuration(service, pub) |
tests/utilities.py | ||
---|---|---|
33 | 33 |
pickle_app_dir = None |
34 | 34 |
sql_app_dir = None |
35 | 35 |
sql_db_name = None |
36 |
legacy_theme_app_dir = None |
|
37 | 36 |
lazy_app_dir = None |
38 | 37 | |
39 | 38 | |
40 | 39 |
known_elements = KnownElements() |
41 | 40 | |
42 | 41 | |
43 |
def create_temporary_pub(pickle_mode=False, legacy_theme_mode=False, lazy_mode=False):
|
|
42 |
def create_temporary_pub(pickle_mode=False, lazy_mode=False): |
|
44 | 43 |
if get_publisher(): |
45 | 44 |
get_publisher().cleanup() |
46 | 45 |
cleanup() |
47 |
if legacy_theme_mode and known_elements.legacy_theme_app_dir: |
|
48 |
APP_DIR = known_elements.legacy_theme_app_dir |
|
49 |
elif lazy_mode and known_elements.lazy_app_dir: |
|
46 |
if lazy_mode and known_elements.lazy_app_dir: |
|
50 | 47 |
APP_DIR = known_elements.lazy_app_dir |
51 | 48 |
elif pickle_mode and known_elements.pickle_app_dir: |
52 | 49 |
APP_DIR = known_elements.pickle_app_dir |
53 |
elif not (legacy_theme_mode or lazy_mode or pickle_mode) and known_elements.sql_app_dir:
|
|
50 |
elif not (lazy_mode or pickle_mode) and known_elements.sql_app_dir: |
|
54 | 51 |
APP_DIR = known_elements.sql_app_dir |
55 | 52 |
else: |
56 | 53 |
APP_DIR = tempfile.mkdtemp() |
57 |
if legacy_theme_mode: |
|
58 |
known_elements.legacy_theme_app_dir = APP_DIR |
|
59 |
elif lazy_mode: |
|
54 |
if lazy_mode: |
|
60 | 55 |
known_elements.lazy_app_dir = APP_DIR |
61 | 56 |
elif pickle_mode: |
62 | 57 |
known_elements.pickle_app_dir = APP_DIR |
... | ... | |
124 | 119 |
'frontoffice-url': 'http://example.net', |
125 | 120 |
} |
126 | 121 |
pub.cfg['language'] = {'language': 'en'} |
127 | ||
128 |
if legacy_theme_mode: |
|
129 |
pub.cfg['branding'] = {'theme': 'default'} |
|
130 |
else: |
|
131 |
pub.cfg['branding'] = {'theme': 'django'} |
|
132 | ||
133 | 122 |
pub.write_cfg() |
134 | 123 | |
135 | 124 |
if not created: |
... | ... | |
183 | 172 |
def clean_temporary_pub(): |
184 | 173 |
if get_publisher(): |
185 | 174 |
get_publisher().cleanup() |
186 |
if known_elements.legacy_theme_app_dir and os.path.exists(known_elements.legacy_theme_app_dir): |
|
187 |
shutil.rmtree(known_elements.legacy_theme_app_dir) |
|
188 |
known_elements.legacy_theme_app_dir = None |
|
189 | 175 |
if known_elements.pickle_app_dir and os.path.exists(known_elements.pickle_app_dir): |
190 | 176 |
shutil.rmtree(known_elements.pickle_app_dir) |
191 | 177 |
known_elements.pickle_app_dir = None |
wcs/admin/settings.py | ||
---|---|---|
24 | 24 |
import lasso |
25 | 25 |
except ImportError: |
26 | 26 |
lasso = None |
27 |
import shutil |
|
28 | 27 |
import xml.etree.ElementTree as ET |
29 | 28 |
import zipfile |
30 | 29 | |
31 |
from django.utils.encoding import force_bytes, force_text
|
|
30 |
from django.utils.encoding import force_bytes |
|
32 | 31 |
from quixote import get_publisher, get_request, get_response, get_session, redirect |
33 | 32 |
from quixote.directory import Directory |
34 | 33 |
from quixote.html import TemplateIO, htmltext |
... | ... | |
41 | 40 |
from wcs.qommon import _, errors, get_cfg, ident, misc, template |
42 | 41 |
from wcs.qommon.admin.cfg import cfg_submit |
43 | 42 |
from wcs.qommon.admin.emails import EmailsDirectory |
44 |
from wcs.qommon.admin.menu import error_page |
|
45 | 43 |
from wcs.qommon.admin.settings import SettingsDirectory as QommonSettingsDirectory |
46 | 44 |
from wcs.qommon.admin.texts import TextsDirectory |
47 | 45 |
from wcs.qommon.afterjobs import AfterJob |
... | ... | |
59 | 57 |
SingleSelectWidget, |
60 | 58 |
StringWidget, |
61 | 59 |
TextWidget, |
62 |
UrlWidget, |
|
63 | 60 |
WidgetList, |
64 | 61 |
) |
65 | 62 |
from wcs.workflows import Workflow, WorkflowImportError |
... | ... | |
449 | 446 |
return r.getvalue() |
450 | 447 | |
451 | 448 | |
452 |
class ThemePreviewDirectory(Directory): |
|
453 |
def _q_traverse(self, path): |
|
454 |
if len(path) < 2: |
|
455 |
return error_page('settings', _('Invalid URL')) |
|
456 | ||
457 |
theme_id = path[0] |
|
458 |
branding = get_publisher().cfg.get('branding', {}) |
|
459 |
original_branding = branding.copy() |
|
460 |
get_publisher().cfg['branding'] = branding |
|
461 |
get_publisher().cfg['branding']['theme'] = theme_id |
|
462 |
if 'template' in get_publisher().cfg['branding']: |
|
463 |
del get_publisher().cfg['branding']['template'] |
|
464 | ||
465 |
root_directory = get_publisher().root_directory_class() |
|
466 | ||
467 |
response = get_response() |
|
468 |
response.reset_includes() |
|
469 |
response.filter = {} |
|
470 |
del response.breadcrumb |
|
471 | ||
472 |
if path[1] in ('backoffice', 'admin') or get_request().get_method() == 'POST': |
|
473 |
from wcs.qommon.template import error_page as base_error_page |
|
474 | ||
475 |
output = base_error_page(_("The theme preview doesn't support this.")) |
|
476 |
else: |
|
477 |
output = root_directory._q_traverse(path[1:]) |
|
478 | ||
479 |
from wcs.qommon.template import decorate |
|
480 | ||
481 |
if isinstance(output, template.QommonTemplateResponse): |
|
482 |
output = template.render(output.templates, output.context) |
|
483 |
theme_preview = decorate(output, response) |
|
484 | ||
485 |
# restore original branding in case it has been changed |
|
486 |
get_publisher().cfg['branding'] = original_branding |
|
487 |
get_publisher().write_cfg() |
|
488 |
response.filter['raw'] = True |
|
489 | ||
490 |
return theme_preview |
|
491 | ||
492 | ||
493 | 449 |
class SettingsDirectory(QommonSettingsDirectory): |
494 | 450 |
_q_exports = [ |
495 | 451 |
'', |
496 |
'themes', |
|
497 | 452 |
'users', |
498 | 453 |
'template', |
499 | 454 |
'emails', |
... | ... | |
506 | 461 |
'sms', |
507 | 462 |
'certificates', |
508 | 463 |
'texts', |
509 |
'install_theme', |
|
510 |
'download_theme', |
|
511 | 464 |
'postgresql', |
512 | 465 |
('admin-permissions', 'admin_permissions'), |
513 | 466 |
'geolocation', |
514 |
'theme_preview', |
|
515 | 467 |
'filetypes', |
516 | 468 |
('user-templates', 'user_templates'), |
517 | 469 |
('data-sources', 'data_sources'), |
... | ... | |
523 | 475 |
identification = IdentificationDirectory() |
524 | 476 |
users = UsersDirectory() |
525 | 477 |
texts = TextsDirectory() |
526 |
theme_preview = ThemePreviewDirectory() |
|
527 | 478 |
filetypes = FileTypesDirectory() |
528 | 479 |
data_sources = NamedDataSourcesDirectory() |
529 | 480 |
wscalls = NamedWsCallsDirectory() |
... | ... | |
636 | 587 |
_('Language'), |
637 | 588 |
_('Configure site language'), |
638 | 589 |
) |
639 |
if enabled('theme'): |
|
640 |
r += htmltext('<dt><a href="themes">%s</a></dt> <dd>%s</dd>') % (_('Theme'), _('Configure theme')) |
|
641 |
if enabled('template'): |
|
642 |
r += htmltext('<dt><a href="template">%s</a></dt> <dd>%s</dd>') % ( |
|
643 |
_('Template'), |
|
644 |
_('Configure template'), |
|
645 |
) |
|
646 | 590 |
if enabled('geolocation'): |
647 | 591 |
r += htmltext('<dt><a href="geolocation">%s</a></dt> <dd>%s</dd>') % ( |
648 | 592 |
_('Geolocation'), |
... | ... | |
766 | 710 |
get_publisher().write_cfg() |
767 | 711 |
return redirect('.') |
768 | 712 | |
769 |
def themes(self): |
|
770 |
request = get_request() |
|
771 | ||
772 |
if 'theme' not in request.form: |
|
773 |
current_theme = get_cfg('branding', {}).get('theme', 'default') |
|
774 | ||
775 |
get_response().breadcrumb.append(('themes', _('Themes'))) |
|
776 |
html_top('settings', title=_('Themes')) |
|
777 |
r = TemplateIO(html=True) |
|
778 |
r += htmltext("<h2>%s</h2>") % _('Themes') |
|
779 | ||
780 |
r += get_session().display_message() |
|
781 | ||
782 |
r += htmltext('<a rel="popup" href="install_theme">%s</a>') % _('Install New Theme') |
|
783 | ||
784 |
r += htmltext('<form action="themes" enctype="multipart/form-data" method="post">') |
|
785 |
themes = template.get_themes_dict() |
|
786 |
r += htmltext('<ul class="biglist themes">') |
|
787 |
for theme, theme_dict in sorted(themes.items()): |
|
788 |
label = theme_dict.get('label') |
|
789 |
if 'version' in theme_dict: |
|
790 |
label = '%s (%s)' % (label, theme_dict.get('version')) |
|
791 |
if current_theme == theme: |
|
792 |
checked = ' checked="checked"' |
|
793 |
else: |
|
794 |
checked = '' |
|
795 |
r += htmltext('<li>') |
|
796 |
r += htmltext('<strong class="label"><label>') |
|
797 |
r += htmltext(' <input name="theme" value="%s" type="radio"%s>%s</input>') % ( |
|
798 |
theme, |
|
799 |
checked, |
|
800 |
label, |
|
801 |
) |
|
802 |
r += htmltext('</label></strong>') |
|
803 |
if theme_dict.get('icon'): |
|
804 |
r += htmltext('<img src="/themes/%s/icon.png" alt="" class="theme-icon" />') % theme |
|
805 |
r += htmltext('<p class="details">%s') % theme_dict.get('desc', '') |
|
806 |
r += htmltext(' [<a href="download_theme?theme=%s">%s</a>]') % (theme, _('download')) |
|
807 |
r += htmltext(' [<a class="theme-preview" href="theme_preview/%s/">%s</a>]') % ( |
|
808 |
theme, |
|
809 |
_('preview'), |
|
810 |
) |
|
811 |
if theme_dict.get('author'): |
|
812 |
r += htmltext('<br/>') |
|
813 |
r += htmltext(_('by %s')) % theme_dict.get('author') |
|
814 |
r += htmltext('</p>') |
|
815 |
r += htmltext('</li>') |
|
816 |
r += htmltext('</ul>') |
|
817 |
r += htmltext('<div class="buttons">') |
|
818 |
r += htmltext('<button>%s</button>') % _('Submit') |
|
819 |
r += htmltext('</div>') |
|
820 |
r += htmltext('</form>') |
|
821 |
return r.getvalue() |
|
822 |
else: |
|
823 |
themes = template.get_themes() |
|
824 |
if str(request.form['theme']) in themes: |
|
825 |
branding_cfg = get_cfg('branding', {}) |
|
826 |
branding_cfg['theme'] = str(request.form['theme']) |
|
827 |
get_publisher().cfg['branding'] = branding_cfg |
|
828 |
get_publisher().write_cfg() |
|
829 |
return redirect('.') |
|
830 | ||
831 |
def download_theme(self): |
|
832 |
theme_id = get_request().form.get('theme') |
|
833 |
if not theme_id: |
|
834 |
return redirect('themes') |
|
835 | ||
836 |
theme_directory = template.get_theme_directory(theme_id) |
|
837 |
if not theme_directory: |
|
838 |
return redirect('themes') |
|
839 | ||
840 |
parent_theme_directory = os.path.dirname(theme_directory) |
|
841 |
c = io.BytesIO() |
|
842 |
with zipfile.ZipFile(c, 'w') as z: |
|
843 |
for base, dummy, filenames in os.walk(theme_directory): |
|
844 |
basetheme = base[len(parent_theme_directory) + 1 :] |
|
845 |
for filename in filenames: |
|
846 |
z.write(os.path.join(base, filename), os.path.join(basetheme, filename)) |
|
847 | ||
848 |
response = get_response() |
|
849 |
response.set_content_type('application/zip') |
|
850 |
response.set_header('content-disposition', 'attachment; filename=%s.zip' % theme_id) |
|
851 |
return c.getvalue() |
|
852 | ||
853 |
def install_theme(self): |
|
854 |
form = Form(enctype='multipart/form-data') |
|
855 |
form.add(FileWidget, 'file', title=_('Theme File'), required=False) |
|
856 |
form.add(UrlWidget, 'url', title=_('Theme Address'), required=False, size=50) |
|
857 |
form.add_submit('submit', _('Install')) |
|
858 |
form.add_submit('cancel', _('Cancel')) |
|
859 | ||
860 |
if form.get_submit() == 'cancel': |
|
861 |
return redirect('.') |
|
862 | ||
863 |
if form.is_submitted() and not form.has_errors(): |
|
864 |
try: |
|
865 |
return self.install_theme_submit(form) |
|
866 |
except ValueError: |
|
867 |
form.get_widget('file').set_error(_('Invalid Theme')) |
|
868 | ||
869 |
get_response().breadcrumb.append(('install_theme', _('Install Theme'))) |
|
870 |
html_top('forms', title=_('Install Theme')) |
|
871 |
r = TemplateIO(html=True) |
|
872 |
r += htmltext('<h2>%s</h2>') % _('Install Theme') |
|
873 |
r += htmltext('<p>%s</p>') % _( |
|
874 |
'You can install a new theme by uploading a file or by pointing to the theme URL.' |
|
875 |
) |
|
876 |
r += form.render() |
|
877 |
return r.getvalue() |
|
878 | ||
879 |
def install_theme_submit(self, form): |
|
880 |
if form.get_widget('url').parse(): |
|
881 |
return self.install_theme_from_url(form.get_widget('url').parse()) |
|
882 |
if form.get_widget('file').parse(): |
|
883 |
return self.install_theme_from_file(form.get_widget('file').parse().fp) |
|
884 |
get_session().message = ('error', _('You have to enter a file or a URL.')) |
|
885 |
return redirect('themes') |
|
886 | ||
887 |
def install_theme_from_file(self, fp): |
|
888 |
try: |
|
889 |
with zipfile.ZipFile(fp, 'r') as z: |
|
890 |
theme_dir = os.path.join(get_publisher().app_dir, 'themes') |
|
891 |
filename_list = [x for x in z.namelist() if x[0] != '/' and x[-1] != '/'] |
|
892 |
if len(filename_list) == 0: |
|
893 |
get_session().message = ('error', _('Empty theme file.')) |
|
894 |
return redirect('themes') |
|
895 |
theme_name = filename_list[0].split('/')[0] |
|
896 |
if ('%s/desc.xml' % theme_name) not in filename_list: |
|
897 |
get_session().message = ('error', _('Theme is missing a desc.xml file.')) |
|
898 |
return redirect('themes') |
|
899 |
desc_xml = z.read('%s/desc.xml' % theme_name) |
|
900 |
theme_dict = template.get_theme_dict(io.StringIO(force_text(desc_xml))) |
|
901 |
if theme_dict.get('name') != theme_name: |
|
902 |
get_session().message = ('error', _('desc.xml is missing a name attribute.')) |
|
903 |
return redirect('themes') |
|
904 |
if os.path.exists(os.path.join(theme_dir, theme_name)): |
|
905 |
shutil.rmtree(os.path.join(theme_dir, theme_name)) |
|
906 |
for f in z.namelist(): |
|
907 |
if f[-1] == '/': |
|
908 |
continue |
|
909 |
path = os.path.join(theme_dir, f) |
|
910 |
data = z.read(f) |
|
911 |
if not os.path.exists(os.path.dirname(path)): |
|
912 |
os.makedirs(os.path.dirname(path)) |
|
913 |
with open(path, 'wb') as _f: |
|
914 |
_f.write(data) |
|
915 |
return redirect('themes') |
|
916 |
except Exception as e: |
|
917 |
get_session().message = ('error', _('Failed to read theme file. (%s)') % str(e)) |
|
918 |
return redirect('themes') |
|
919 | ||
920 |
def install_theme_from_url(self, url): |
|
921 |
try: |
|
922 |
fp = misc.urlopen(url) |
|
923 |
except misc.ConnectionError as e: |
|
924 |
get_session().message = ('error', _('Error loading theme (%s).') % str(e)) |
|
925 |
return redirect('themes') |
|
926 | ||
927 |
return self.install_theme_from_file(io.StringIO(fp.read())) |
|
928 | ||
929 |
def template(self): |
|
930 |
from wcs.qommon.template import get_default_ezt_template |
|
931 | ||
932 |
default_template_ezt = get_default_ezt_template() |
|
933 |
branding_cfg = get_cfg('branding', {}) |
|
934 |
template = branding_cfg.get('template', default_template_ezt) |
|
935 |
form = Form(enctype="multipart/form-data") |
|
936 |
form.add(TextWidget, 'template', title=_('Site Template'), value=template, cols=80, rows=25) |
|
937 |
form.add_submit('submit', _('Submit')) |
|
938 |
form.add_submit('restore-default', _('Restore default template')) |
|
939 |
form.add_submit('cancel', _('Cancel')) |
|
940 |
if form.get_widget('cancel').parse(): |
|
941 |
return redirect('.') |
|
942 | ||
943 |
if form.get_submit() == 'cancel': |
|
944 |
return redirect('.') |
|
945 | ||
946 |
if form.get_submit() == 'restore-default': |
|
947 |
self.template_submit() |
|
948 |
return redirect('.') |
|
949 | ||
950 |
if form.is_submitted() and not form.has_errors(): |
|
951 |
self.template_submit(form) |
|
952 |
return redirect('.') |
|
953 | ||
954 |
get_response().breadcrumb.append(('template', _('Template'))) |
|
955 |
html_top('settings', title=_('Template')) |
|
956 |
r = TemplateIO(html=True) |
|
957 |
r += htmltext('<h2>%s</h2>') % _('Template') |
|
958 |
r += form.render() |
|
959 |
return r.getvalue() |
|
960 | ||
961 |
def template_submit(self, form=None): |
|
962 |
from wcs.qommon.template import DEFAULT_TEMPLATE_EZT, get_default_ezt_template |
|
963 | ||
964 |
theme_default_template_ezt = get_default_ezt_template() |
|
965 | ||
966 |
get_publisher().reload_cfg() |
|
967 |
branding_cfg = get_cfg('branding', {}) |
|
968 |
if not form: |
|
969 |
template = None |
|
970 |
else: |
|
971 |
template = form.get_widget('template').parse() |
|
972 |
if template in (DEFAULT_TEMPLATE_EZT, theme_default_template_ezt) or not template: |
|
973 |
if 'template' in branding_cfg: |
|
974 |
del branding_cfg['template'] |
|
975 |
else: |
|
976 |
branding_cfg['template'] = template |
|
977 |
get_publisher().cfg['branding'] = branding_cfg |
|
978 |
get_publisher().write_cfg() |
|
979 | ||
980 | 713 |
def export(self): |
981 | 714 |
if get_request().form.get('download'): |
982 | 715 |
return self.export_download() |
wcs/compat.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU General Public License |
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import os |
|
18 | 17 |
from contextlib import contextmanager |
19 | 18 |
from threading import Lock |
20 | 19 | |
... | ... | |
31 | 30 |
from .publisher import WcsPublisher |
32 | 31 |
from .qommon import force_str, template |
33 | 32 |
from .qommon.http_request import HTTPRequest |
34 |
from .qommon.publisher import get_cfg, set_publisher_class
|
|
33 |
from .qommon.publisher import set_publisher_class |
|
35 | 34 | |
36 | 35 | |
37 | 36 |
class TemplateWithFallbackView(TemplateView): |
... | ... | |
65 | 64 |
elif request.headers.get('X-Popup') == 'true': |
66 | 65 |
response = HttpResponse('<div class="popup-content">%s</div>' % context['body']) |
67 | 66 |
elif 'raw' in (getattr(self.quixote_response, 'filter') or {}): |
68 |
# used for theme preview (generated in /backoffice/ but cannot
|
|
69 |
# obviously receive the admin template.
|
|
67 |
# used for raw HTML snippets (for example in the test tool
|
|
68 |
# results in inspect page).
|
|
70 | 69 |
response = HttpResponse(context['body']) |
71 | 70 |
else: |
72 | 71 |
response = self.render_to_response(context) |
... | ... | |
145 | 144 |
return output |
146 | 145 |
if request.headers.get('X-Popup') == 'true': |
147 | 146 |
return '<div class="popup-content">%s</div>' % output |
148 |
if response.filter and response.filter.get('admin_ezt'): |
|
149 |
return self.render_response(output) |
|
150 | ||
151 |
current_theme = get_cfg('branding', {}).get('theme', 'default') |
|
152 |
theme_directory = template.get_theme_directory(current_theme) |
|
153 |
if not theme_directory: |
|
154 |
return self.render_response(output) |
|
155 | ||
156 |
if not os.path.exists(os.path.join(theme_directory, 'templates')): |
|
157 |
return self.render_response(output) |
|
158 | ||
159 |
if not os.path.exists(os.path.join(theme_directory, 'templates/wcs/base.html')): |
|
160 |
return self.render_response(output) |
|
161 | 147 | |
162 | 148 |
if isinstance(output, template.QommonTemplateResponse): |
163 | 149 |
template_response = output |
wcs/qommon/publisher.py | ||
---|---|---|
116 | 116 |
after_login_url = '' |
117 | 117 |
qommon_static_dir = 'static/' |
118 | 118 |
qommon_admin_css = 'css/dc2/admin.css' |
119 |
default_theme = 'default'
|
|
119 |
default_theme = 'django'
|
|
120 | 120 | |
121 | 121 |
site_options = None |
122 | 122 |
site_charset = 'utf-8' |
... | ... | |
346 | 346 | |
347 | 347 |
return error_page |
348 | 348 | |
349 |
def render_response(self, content): |
|
350 |
if isinstance(content, template.QommonTemplateResponse): |
|
351 |
content = template.render(content.templates, content.context) |
|
352 |
return template.decorate(content, self.get_request().response) |
|
353 | ||
354 | 349 |
def install_lang(self, request=None): |
355 | 350 |
if request: |
356 | 351 |
lang = request.language |
wcs/qommon/template.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU General Public License |
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import glob |
|
18 | 17 |
import io |
19 | 18 |
import os |
20 | 19 |
import re |
21 |
import xml.etree.ElementTree as ET |
|
22 | 20 | |
23 | 21 |
import django.template |
24 | 22 |
from django.template import TemplateSyntaxError as DjangoTemplateSyntaxError |
... | ... | |
27 | 25 |
from django.template.loader import render_to_string |
28 | 26 |
from django.utils.encoding import force_text, smart_text |
29 | 27 |
from quixote import get_publisher, get_request, get_response, get_session |
30 |
from quixote.directory import Directory |
|
31 | 28 |
from quixote.html import TemplateIO, htmlescape, htmltext |
32 |
from quixote.util import StaticDirectory, StaticFile |
|
33 | 29 | |
34 | 30 |
from . import ezt, force_str |
35 | 31 | |
... | ... | |
53 | 49 |
return location |
54 | 50 | |
55 | 51 | |
56 |
class ThemesDirectory(Directory): |
|
57 |
def _q_lookup(self, component): |
|
58 |
from . import errors |
|
59 | ||
60 |
if component in ('.', '..'): |
|
61 |
raise errors.TraversalError() |
|
62 | ||
63 |
location = get_theme_directory(component) |
|
64 |
if location is None: |
|
65 |
raise errors.TraversalError() |
|
66 | ||
67 |
if os.path.isdir(location): |
|
68 |
return StaticDirectory(location) |
|
69 |
else: |
|
70 |
return StaticFile(location) |
|
71 | ||
72 | ||
73 |
def get_themes_dict(): |
|
74 |
system_location = os.path.join(get_publisher().data_dir, 'themes') |
|
75 |
local_location = os.path.join(get_publisher().app_dir, 'themes') |
|
76 | ||
77 |
themes = {} |
|
78 |
for theme_xml in glob.glob(os.path.join(system_location, '*/desc.xml')) + glob.glob( |
|
79 |
os.path.join(local_location, '*/desc.xml') |
|
80 |
): |
|
81 |
theme_dict = get_theme_dict(theme_xml) |
|
82 |
if not theme_dict: |
|
83 |
continue |
|
84 |
themes[theme_dict.get('name')] = theme_dict |
|
85 |
return themes |
|
86 | ||
87 | ||
88 |
def get_theme_dict(theme_xml): |
|
89 |
try: |
|
90 |
tree = ET.parse(theme_xml).getroot() |
|
91 |
except Exception: # parse error |
|
92 |
return None |
|
93 |
name = force_str(tree.attrib['name']) |
|
94 |
version = force_str(tree.attrib.get('version') or '') |
|
95 |
label = force_str(tree.findtext('label') or '') |
|
96 |
desc = force_str(tree.findtext('desc') or '') |
|
97 |
author = force_str(tree.findtext('author') or '') |
|
98 |
icon = None |
|
99 |
if isinstance(theme_xml, str): |
|
100 |
icon = os.path.join(os.path.dirname(theme_xml), 'icon.png') |
|
101 |
if not os.path.exists(icon): |
|
102 |
icon = None |
|
103 |
theme = {'name': name, 'label': label, 'desc': desc, 'author': author, 'icon': icon, 'version': version} |
|
104 |
theme['keywords'] = [] |
|
105 |
for keyword in tree.findall('keywords/keyword'): |
|
106 |
theme['keywords'].append(keyword.text) |
|
107 |
return theme |
|
108 | ||
109 | ||
110 |
def get_themes(): |
|
111 |
# backward compatibility function, it returns a tuple with theme info, |
|
112 |
# newer code should use get_themes_dict() |
|
113 |
themes = {} |
|
114 |
for k, v in get_themes_dict().items(): |
|
115 |
themes[k] = (v['label'], v['desc'], v['author'], v['icon']) |
|
116 |
return themes |
|
117 | ||
118 | ||
119 |
def get_current_theme(): |
|
120 |
from .publisher import get_cfg |
|
121 | ||
122 |
current_theme = get_cfg('branding', {}).get('theme', 'default') |
|
123 |
system_location = os.path.join(get_publisher().data_dir, 'themes', current_theme) |
|
124 |
local_location = os.path.join(get_publisher().app_dir, 'themes', current_theme) |
|
125 |
for location in (local_location, system_location): |
|
126 |
if os.path.exists(location): |
|
127 |
return get_theme_dict(os.path.join(location, 'desc.xml')) |
|
128 |
default_theme_location = os.path.join(get_publisher().data_dir, 'themes', 'default') |
|
129 |
return get_theme_dict(os.path.join(default_theme_location, 'desc.xml')) |
|
130 | ||
131 | ||
132 |
DEFAULT_TEMPLATE_EZT = """<!DOCTYPE html> |
|
133 |
<html lang="[site_lang]"> |
|
134 |
<head> |
|
135 |
<title>[page_title]</title> |
|
136 |
<link rel="stylesheet" type="text/css" href="[css]"/> |
|
137 |
[script] |
|
138 |
</head> |
|
139 |
<body[if-any onload] onload="[onload]"[end]> |
|
140 |
<div id="page"> |
|
141 |
<div id="top"> <h1>[if-any title][title][else][site_name][end]</h1> </div> |
|
142 |
<div id="main-content"> |
|
143 |
[if-any breadcrumb]<p id="breadcrumb">[breadcrumb]</p>[end] |
|
144 |
[body] |
|
145 |
</div> |
|
146 |
<div id="footer">[if-any footer][footer][end]</div> |
|
147 |
</div> |
|
148 |
</body> |
|
149 |
</html>""" |
|
150 | ||
151 |
DEFAULT_IFRAME_EZT = """<!DOCTYPE html> |
|
152 |
<html lang="[site_lang]"> |
|
153 |
<head> |
|
154 |
<title>[page_title]</title> |
|
155 |
<link rel="stylesheet" type="text/css" href="[css]"/> |
|
156 |
[script] |
|
157 |
</head> |
|
158 |
<body[if-any onload] onload="[onload]"[end]> |
|
159 |
<div id="main-content"> |
|
160 |
[if-any breadcrumb]<p id="breadcrumb">[breadcrumb]</p>[end] |
|
161 |
[body] |
|
162 |
</div> |
|
163 |
</body> |
|
164 |
</html>""" |
|
165 | ||
166 |
default_template = ezt.Template() |
|
167 |
default_template.parse(DEFAULT_TEMPLATE_EZT) |
|
168 | ||
169 |
default_iframe_template = ezt.Template() |
|
170 |
default_iframe_template.parse(DEFAULT_IFRAME_EZT) |
|
171 | ||
172 | ||
173 | 52 |
def html_top(title=None, default_org=None): |
174 | 53 |
if not hasattr(get_response(), 'filter'): |
175 | 54 |
get_response().filter = {} |
... | ... | |
207 | 86 |
return htmltext(r.getvalue()) |
208 | 87 | |
209 | 88 | |
210 |
def get_default_ezt_template(): |
|
211 |
from .publisher import get_cfg |
|
212 | ||
213 |
current_theme = get_cfg('branding', {}).get('theme', 'default') |
|
214 | ||
215 |
filename = os.path.join( |
|
216 |
get_publisher().app_dir, 'themes', current_theme, 'template.%s.ezt' % get_publisher().APP_NAME |
|
217 |
) |
|
218 |
if os.path.exists(filename): |
|
219 |
with open(filename) as fd: |
|
220 |
return fd.read() |
|
221 | ||
222 |
filename = os.path.join( |
|
223 |
get_publisher().data_dir, 'themes', current_theme, 'template.%s.ezt' % get_publisher().APP_NAME |
|
224 |
) |
|
225 |
if os.path.exists(filename): |
|
226 |
with open(filename) as fd: |
|
227 |
return fd.read() |
|
228 | ||
229 |
filename = os.path.join(get_publisher().app_dir, 'themes', current_theme, 'template.ezt') |
|
230 |
if os.path.exists(filename): |
|
231 |
with open(filename) as fd: |
|
232 |
return fd.read() |
|
233 | ||
234 |
filename = os.path.join(get_publisher().data_dir, 'themes', current_theme, 'template.ezt') |
|
235 |
if os.path.exists(filename): |
|
236 |
with open(filename) as fd: |
|
237 |
return fd.read() |
|
238 | ||
239 |
return DEFAULT_TEMPLATE_EZT |
|
240 | ||
241 | ||
242 | 89 |
def get_decorate_vars(body, response, generate_breadcrumb=True, **kwargs): |
243 | 90 |
from .publisher import get_cfg |
244 | 91 | |
... | ... | |
303 | 150 |
subtitle = kwargs.get('subtitle') |
304 | 151 |
sidebar = kwargs.get('sidebar') |
305 | 152 |
css = root_url + get_publisher().qommon_static_dir + get_publisher().qommon_admin_css |
306 | ||
307 |
app_dir_filename = os.path.join(get_publisher().app_dir, 'themes', current_theme, 'admin.css') |
|
308 |
data_dir_filename = os.path.join(get_publisher().data_dir, 'themes', current_theme, 'admin.css') |
|
309 |
for filename in (app_dir_filename, data_dir_filename): |
|
310 |
if os.path.exists(filename): |
|
311 |
extra_css = root_url + 'themes/%s/admin.css' % current_theme |
|
312 |
break |
|
313 | 153 |
extra_head = get_publisher().get_site_option('backoffice_extra_head') |
314 | 154 |
app_label = get_publisher().get_site_option('app_label') or 'w.c.s.' |
315 | 155 |
else: |
316 |
if current_theme == 'default': |
|
317 |
css = root_url + 'static/css/%s.css' % get_publisher().APP_NAME |
|
318 |
else: |
|
319 |
css = root_url + 'themes/%s/%s.css' % (current_theme, get_publisher().APP_NAME) |
|
156 |
css = root_url + 'themes/%s/%s.css' % (current_theme, get_publisher().APP_NAME) |
|
320 | 157 | |
321 | 158 |
# this variable is kept in locals() as it was once part of the default |
322 | 159 |
# template and existing installations may have template changes that |
... | ... | |
354 | 191 |
return vars |
355 | 192 | |
356 | 193 | |
357 |
def decorate(body, response): |
|
358 |
if get_request().get_header('x-popup') == 'true': |
|
359 |
return '''<div class="popup-content"> %s </div>''' % body |
|
360 | ||
361 |
from .publisher import get_cfg |
|
362 | ||
363 |
generate_breadcrumb = True |
|
364 |
template_ezt = get_cfg('branding', {}).get('template') |
|
365 |
current_theme = get_cfg('branding', {}).get('theme', 'default') |
|
366 |
if not template_ezt: |
|
367 |
# the theme can provide a default template |
|
368 |
possible_filenames = [] |
|
369 |
possible_filenames.append('template.%s.ezt' % get_publisher().APP_NAME) |
|
370 |
possible_filenames.append('template.ezt') |
|
371 | ||
372 |
possible_dirnames = [ |
|
373 |
os.path.join(get_publisher().app_dir, 'themes', current_theme), |
|
374 |
os.path.join(get_publisher().data_dir, 'themes', current_theme), |
|
375 |
os.path.join(get_publisher().data_dir, 'themes', 'default'), |
|
376 |
] |
|
377 | ||
378 |
for fname in possible_filenames: |
|
379 |
for dname in possible_dirnames: |
|
380 |
filename = os.path.join(dname, fname) |
|
381 |
if os.path.exists(filename): |
|
382 |
with open(filename) as fd: |
|
383 |
template_ezt = fd.read() |
|
384 |
break |
|
385 |
else: |
|
386 |
continue |
|
387 |
break |
|
388 | ||
389 |
if template_ezt: |
|
390 |
generate_breadcrumb = '[breadcrumb]' in template_ezt |
|
391 | ||
392 |
template = ezt.Template() |
|
393 |
template.parse(template_ezt) |
|
394 |
else: |
|
395 |
template = default_template |
|
396 | ||
397 |
fd = io.StringIO() |
|
398 |
vars = get_decorate_vars(body, response, generate_breadcrumb=generate_breadcrumb) |
|
399 | ||
400 |
template.generate(fd, vars) |
|
401 |
return fd.getvalue() |
|
402 | ||
403 | ||
404 | 194 |
def render(template_name, context): |
405 | 195 |
request = getattr(get_request(), 'django_request', None) |
406 | 196 |
result = render_to_string(template_name, context, request=request) |
wcs/root.py | ||
---|---|---|
265 | 265 |
] |
266 | 266 | |
267 | 267 |
api = ApiDirectory() |
268 |
themes = template.ThemesDirectory() |
|
269 | 268 |
myspace = MyspaceDirectory() |
270 | 269 |
pages = PagesDirectory() |
271 | 270 |
fargo = portfolio.FargoDirectory() |
wcs/utils.py | ||
---|---|---|
38 | 38 |
template_dirs.append(os.path.join(get_publisher().app_dir, 'templates')) |
39 | 39 |
template_dirs.append(os.path.join(get_publisher().app_dir, 'theme', 'templates')) |
40 | 40 | |
41 |
current_theme = get_cfg('branding', {}).get('theme', 'default')
|
|
41 |
current_theme = get_cfg('branding', {}).get('theme', get_publisher().default_theme)
|
|
42 | 42 |
theme_directory = get_theme_directory(current_theme) |
43 | 43 |
if theme_directory: |
44 | 44 |
# templates from theme directory |
45 |
- |