general: remove legacy theming code (#70128)

This commit is contained in:
Frédéric Péters 2022-10-11 17:26:52 +02:00
parent 36110d8082
commit 2d71198352
22 changed files with 16 additions and 971 deletions

View File

@ -4,8 +4,6 @@ include wcs.cfg-sample
recursive-include wcs/locale *.po *.mo recursive-include wcs/locale *.po *.mo
recursive-include extra/ *.py recursive-include extra/ *.py
recursive-include data/web/ *.html *.css *.png recursive-include data/web/ *.html *.css *.png
recursive-include data/themes/default/ *.html *.css *.png *.gif *.jpg *.js *.ezt *.xml
recursive-include data/themes/alto/ *.html *.css *.png *.gif *.jpg *.js *.ezt *.xml
recursive-include data/vendor/ *.dat recursive-include data/vendor/ *.dat
recursive-include wcs/qommon/static/ *.css *.scss *.png *.gif *.jpg *.js *.eot *.svg *.ttf *.woff *.map recursive-include wcs/qommon/static/ *.css *.scss *.png *.gif *.jpg *.js *.eot *.svg *.ttf *.woff *.map
recursive-include wcs/templates *.html *.txt recursive-include wcs/templates *.html *.txt

View File

@ -1,6 +0,0 @@
<?xml version="1.0"?>
<theme name="alto" version="1.0">
<label>Alto</label>
<desc>Alto theme</desc>
<author>Frederic Peters (original Dotclear theme (alto studio) by David Jubert)</author>
</theme>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 728 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html lang="[site_lang]">
<head>
<title>[page_title]</title>
<link rel="stylesheet" type="text/css" href="[css]"/>
[script]
</head>
<body[if-any onload] onload="[onload]"[end]>
<div id="page">
<div id="top"> <h1>[if-any title][title][else][site_name][end]</h1> </div>
<div id="main-content">
[if-any breadcrumb]<p id="breadcrumb">Vous &ecirc;tes ici : [breadcrumb]</p>[end]
[body]
</div>
<div id="footer"></div>
</div>
</body>
</html>

View File

@ -1,270 +0,0 @@
/* adapted from alto dotclear theme */
@import url(/static/xstatic/themes/smoothness/jquery-ui.min.css);
@import url(/static/css/qommon.css);
html, body {
background: #CCCCCC;
font-family: sans-serif;
color: #333333;
margin: 0;
padding: 0;
text-align: center;
height: 100%;
margin-bottom: 1px;
}
fieldset {
border: none;
}
label {
cursor: pointer;
cursor: hand;
}
img {
border: 0;
}
input,textarea {
border: 1px solid #999;
}
textarea {
width: 99%;
}
a {
color: #000;
text-decoration : none;
}
a:hover {
color: #0273B9;
text-decoration : underline;
}
a:visited {
color: #0273B9;
text-decoration : none;
}
#page {
background: #fff url(img/page.jpg) repeat-y center top;
color: inherit;
width: 886px;
margin: 0 auto;
text-align: left;
padding: 0px;
}
#top {
margin: 0;
padding: 0;
background: #CCCCCC url(img/top.jpg) no-repeat left top;
margin-bottom: 2em;
}
#top h1 {
width: 706px;
margin: 0 auto;
padding-top: 70px;
}
#side {
float: right;
width: 204px;
padding: 0;
margin: 0 -20px 0 20px;
}
#side #tracking-code {
margin-bottom: 1em;
border: 1px solid #bfbfbf;
color: #333333;
background: #e6e6e6;
padding: 1ex;
}
#side #tracking-code h3 {
margin: 0;
}
#side #tracking-code button,
#side #tracking-code a {
margin: 1ex auto;
display: block;
text-align: center;
font-size: 120%;
background: white;
border: 1px solid black;
padding: 0.5ex 0;
width: 10em;
}
#side #tracking-code button {
background: #0273B9;
color: white;
}
input[name=savedraft] {
display: none;
}
#steps {
background: white;
border: 1px solid #bfbfbf;
color: #333333;
background: #e6e6e6;
-moz-border-radius: 6px;
text-align: left;
}
#footer {
width: 886px;
height: 123px;
background: #CCCCCC url(img/bottom.jpg) no-repeat left top;
margin: 0;
margin-top: 1em;
color: #666;
clear: both;
}
#footer p {
width: 706px;
margin: 0 auto;
padding-top: 24px;
text-align: right;
font-size: 80%;
}
#main-content {
width: 735px;
padding-left: 65px;
text-align: justify;
}
div#steps ol {
list-style: none;
margin: 0;
padding: 0.5em;
}
div#steps li {
display: block;
border: 1px solid #ddd;
margin: 0.5em 0;
background: #eee;
color: #aaa;
}
#steps span.marker {
padding: 0 1ex 0 1ex;
font-weight: bold;
color: white;
text-align: center;
background: #ddd;
}
#steps li.current span.marker {
background: #0273b9;
}
#steps li.current {
font-weight: bold;
border: 1px solid #333333;
}
#steps li.current span.label {
color: #333333;
}
#steps ol ul {
margin-right: 1em;
font-size: 90%;
}
#steps ol ul li {
padding: 0 2px;
font-weight: normal;
margin-left: -1ex;
}
#steps ol ul li.current {
border-color: inherit;
color: #333333;
}
div.widget {
clear: none;
margin-bottom: 1.5em;
}
hr {
visibility: hidden;
}
textarea {
}
p#breadcrumb {
background: #e6e6e6;
-moz-border-radius: 6px;
width: 750px;
padding: 3px;
font-size: 90%;
border: 1px solid #bfbfbf;
}
div#receipt {
}
div#receipt span.label {
font-weight: bold;
display: block;
}
div#receipt span.value {
display: block;
margin-left: 1em;
}
form div.page,
div#receipt div.page {
border: 1px solid #bfbfbf;
padding: 1ex;
margin-bottom: 1em;
}
form div.page p,
div#receipt div.page p {
margin-top: 0;
}
form div.page h3,
div#receipt div.page h3 {
margin: 0;
margin-bottom: 1ex;
}
p#receiver {
margin: 0;
margin-left: 2em;
margin-top: -0.7em;
margin-bottom: 1em;
padding: 2px 5px;
font-weight: bold;
}
table#listing {
background: white;
border: 1px solid #888;
}

View File

@ -1,6 +0,0 @@
<?xml version="1.0"?>
<theme name="default" version="1.0">
<label>Default</label>
<desc>Default theme</desc>
<author>Frederic Peters &amp; Dotclear Team</author>
</theme>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 675 B

View File

@ -12,7 +12,7 @@
<div id="page"> <div id="page">
<div id="top"> <div id="top">
{% block header %} {% block header %}
<h1>WIP/DJANGO - {% if title %}{{ title }}{% else %}{{ site_name }}{% endif %}</h1> <h1>{% if title %}{{ title }}{% else %}{{ site_name }}{% endif %}</h1>
{% endblock %} {% endblock %}
</div> </div>
<div id="main-content"> <div id="main-content">

View File

@ -28,7 +28,6 @@ from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef from wcs.formdef import FormDef
from wcs.qommon.form import UploadedFile from wcs.qommon.form import UploadedFile
from wcs.qommon.http_request import HTTPRequest from wcs.qommon.http_request import HTTPRequest
from wcs.qommon.template import get_current_theme
from wcs.wf.export_to_model import ExportToModel from wcs.wf.export_to_model import ExportToModel
from wcs.workflows import Workflow from wcs.workflows import Workflow
from wcs.wscalls import NamedWsCall from wcs.wscalls import NamedWsCall
@ -74,16 +73,14 @@ def test_settings_disabled_screens(pub):
app = login(get_app(pub)) app = login(get_app(pub))
resp = app.get('/backoffice/settings/') resp = app.get('/backoffice/settings/')
assert 'Identification' in resp.text assert 'Identification' in resp.text
assert 'Theme' in resp.text
if not pub.site_options.has_section('options'): if not pub.site_options.has_section('options'):
pub.site_options.add_section('options') pub.site_options.add_section('options')
pub.site_options.set('options', 'settings-disabled-screens', 'identification, theme') pub.site_options.set('options', 'settings-disabled-screens', 'identification')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd) pub.site_options.write(fd)
resp = app.get('/backoffice/settings/') resp = app.get('/backoffice/settings/')
assert 'Identification' not in resp.text assert 'Identification' not in resp.text
assert 'Theme' not in resp.text
def test_settings_export_import(pub): def test_settings_export_import(pub):
@ -318,61 +315,6 @@ def test_settings_export_import(pub):
assert 'Unknown referenced objects [Unknown fields blocks: unknown]' in resp assert 'Unknown referenced objects [Unknown fields blocks: unknown]' in resp
def test_settings_themes(pub):
create_superuser(pub)
app = login(get_app(pub))
# create mock theme
os.mkdir(os.path.join(pub.app_dir, 'themes'))
os.mkdir(os.path.join(pub.app_dir, 'themes', 'test'))
with open(os.path.join(pub.app_dir, 'themes', 'test', 'desc.xml'), 'w') as fd:
fd.write(
'<?xml version="1.0"?>'
'<theme name="test" version="1.0">'
' <label>Test Theme</label>'
'</theme>'
)
resp = app.get('/backoffice/settings/themes')
assert 'biglist themes' in resp.text
assert 'Test Theme (1.0)' in resp.text
# just for the kick, there's no support for uploading file in webtest 1.3
resp = app.get('/backoffice/settings/themes')
resp.click('Install New Theme')
# select the theme
resp = app.get('/backoffice/settings/themes')
resp.forms[0]['theme'].value = 'test'
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/backoffice/settings/'
resp = app.get('/backoffice/settings/themes')
assert 'checked' in resp.text
assert get_current_theme()['name'] == 'test'
def test_settings_template(pub):
create_superuser(pub)
app = login(get_app(pub))
resp = app.get('/backoffice/settings/template')
# change template
orig_value = resp.forms[0]['template'].value
assert 'foobar' not in orig_value
resp.forms[0]['template'] = orig_value + '<!-- foobar -->'
resp = resp.forms[0].submit('submit')
# restore default template
resp = app.get('/backoffice/settings/template')
assert 'foobar' in resp.forms[0]['template'].value
resp = resp.forms[0].submit('restore-default')
# check
resp = app.get('/backoffice/settings/template')
assert resp.forms[0]['template'].value == orig_value
def test_settings_user(pub): def test_settings_user(pub):
user = create_superuser(pub) user = create_superuser(pub)
app = login(get_app(pub)) app = login(get_app(pub))
@ -829,68 +771,6 @@ def test_settings_permissions(pub):
assert pub.cfg['admin-permissions']['workflows'] == [] assert pub.cfg['admin-permissions']['workflows'] == []
def test_settings_theme_preview(pub):
create_superuser(pub)
FormDef.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = []
formdef.store()
app = login(get_app(pub))
assert 'alto/wcs.css' not in app.get('/').text
resp = app.get('/backoffice/settings/themes')
assert resp.form['theme'].value in ('default', 'django')
# visit theme preview
resp = resp.click(href='theme_preview/alto/')
assert 'alto/wcs.css' in resp.text
# get into a form, making sure we are kept in theme preview
resp = resp.click('form title')
assert 'alto/wcs.css' in resp.text
# verify submits are not allowed
resp = resp.form.submit('submit')
assert "The theme preview doesn&#39;t support this." in resp.text
def test_settings_theme_download_upload(pub):
create_superuser(pub)
# download existing theme
app = login(get_app(pub))
resp = app.get('/backoffice/settings/themes')
resp = resp.click('download', index=0)
assert resp.headers['content-type'] == 'application/zip'
zip_content = io.BytesIO(resp.body)
with zipfile.ZipFile(zip_content, 'a') as zipf:
filelist = zipf.namelist()
assert 'alto/icon.png' in filelist
assert 'alto/desc.xml' in filelist
assert 'alto/template.ezt' in filelist
assert 'alto/wcs.css' in filelist
# modify it
zipf.writestr('alto/foobar.txt', 'XXX')
# upload it
resp = app.get('/backoffice/settings/themes')
resp = resp.click('Install New Theme')
resp.form['file'] = Upload('alto-modified.zip', zip_content.getvalue())
resp = resp.form.submit()
assert os.path.exists(os.path.join(pub.app_dir, 'themes/alto/foobar.txt'))
assert app.get('/themes/alto/foobar.txt').text == 'XXX'
assert 'Directory listing denied' in app.get('/themes/alto/', status=200).text
assert app.get('/themes/alto/plop', status=404)
assert app.get('/themes/alto/../', status=404)
assert app.get('/themes/xxx/../', status=404)
def test_postgresql_settings(pub): def test_postgresql_settings(pub):
create_superuser(pub) create_superuser(pub)

View File

@ -343,28 +343,6 @@ def test_category_page_redirect_var(pub):
assert resp.location == 'http://www.example.com/en/foobar/' assert resp.location == 'http://www.example.com/en/foobar/'
def test_legacy_theme_misc():
pub = create_temporary_pub(legacy_theme_mode=True)
pub.cfg['language'] = {'language': 'en'}
pub.write_cfg()
formdef = create_formdef()
formdef.fields = [fields.StringField(id='1', label='string')]
formdef.store()
resp = get_app(pub).get('/')
assert '<title>' in resp.text
assert '/static/js/qommon.forms.js' not in resp.text
assert '<a class="" href="test/">test</a>' in resp.text
resp = resp.click('test')
assert '/static/js/qommon.forms.js' in resp.text
resp.form['f1'] = 'TEST'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit').follow()
assert 'The form has been recorded on' in resp.text
def test_form_access(pub): def test_form_access(pub):
formdef = create_formdef() formdef = create_formdef()
get_app(pub).get('/test/', status=200) get_app(pub).get('/test/', status=200)

View File

@ -314,14 +314,14 @@ def test_update_configuration(setuptest):
def test_update_themes(setuptest): def test_update_themes(setuptest):
pub, hobo_cmd = setuptest pub, hobo_cmd = setuptest
pub.cfg['branding'] = {'theme': 'default'} pub.cfg['branding'] = {'theme': 'django'}
service = [x for x in HOBO_JSON.get('services', []) if x.get('service-id') == 'wcs'][0] service = [x for x in HOBO_JSON.get('services', []) if x.get('service-id') == 'wcs'][0]
hobo_cmd.update_configuration(service, pub) hobo_cmd.update_configuration(service, pub)
assert pub.cfg['branding']['theme'] == 'default' assert pub.cfg['branding']['theme'] == 'django'
service['variables']['theme'] = 'foobar' service['variables']['theme'] = 'foobar'
hobo_cmd.update_configuration(service, pub) hobo_cmd.update_configuration(service, pub)
assert pub.cfg['branding']['theme'] == 'default' assert pub.cfg['branding']['theme'] == 'django'
hobo_cmd.THEMES_DIRECTORY = os.path.join(os.path.dirname(__file__), 'themes') hobo_cmd.THEMES_DIRECTORY = os.path.join(os.path.dirname(__file__), 'themes')
hobo_cmd.update_configuration(service, pub) hobo_cmd.update_configuration(service, pub)

View File

@ -33,30 +33,25 @@ class KnownElements:
pickle_app_dir = None pickle_app_dir = None
sql_app_dir = None sql_app_dir = None
sql_db_name = None sql_db_name = None
legacy_theme_app_dir = None
lazy_app_dir = None lazy_app_dir = None
known_elements = KnownElements() known_elements = KnownElements()
def create_temporary_pub(pickle_mode=False, legacy_theme_mode=False, lazy_mode=False): def create_temporary_pub(pickle_mode=False, lazy_mode=False):
if get_publisher(): if get_publisher():
get_publisher().cleanup() get_publisher().cleanup()
cleanup() cleanup()
if legacy_theme_mode and known_elements.legacy_theme_app_dir: if lazy_mode and known_elements.lazy_app_dir:
APP_DIR = known_elements.legacy_theme_app_dir
elif lazy_mode and known_elements.lazy_app_dir:
APP_DIR = known_elements.lazy_app_dir APP_DIR = known_elements.lazy_app_dir
elif pickle_mode and known_elements.pickle_app_dir: elif pickle_mode and known_elements.pickle_app_dir:
APP_DIR = known_elements.pickle_app_dir APP_DIR = known_elements.pickle_app_dir
elif not (legacy_theme_mode or lazy_mode or pickle_mode) and known_elements.sql_app_dir: elif not (lazy_mode or pickle_mode) and known_elements.sql_app_dir:
APP_DIR = known_elements.sql_app_dir APP_DIR = known_elements.sql_app_dir
else: else:
APP_DIR = tempfile.mkdtemp() APP_DIR = tempfile.mkdtemp()
if legacy_theme_mode: if lazy_mode:
known_elements.legacy_theme_app_dir = APP_DIR
elif lazy_mode:
known_elements.lazy_app_dir = APP_DIR known_elements.lazy_app_dir = APP_DIR
elif pickle_mode: elif pickle_mode:
known_elements.pickle_app_dir = APP_DIR known_elements.pickle_app_dir = APP_DIR
@ -124,12 +119,6 @@ def create_temporary_pub(pickle_mode=False, legacy_theme_mode=False, lazy_mode=F
'frontoffice-url': 'http://example.net', 'frontoffice-url': 'http://example.net',
} }
pub.cfg['language'] = {'language': 'en'} pub.cfg['language'] = {'language': 'en'}
if legacy_theme_mode:
pub.cfg['branding'] = {'theme': 'default'}
else:
pub.cfg['branding'] = {'theme': 'django'}
pub.write_cfg() pub.write_cfg()
if not created: if not created:
@ -183,9 +172,6 @@ def create_temporary_pub(pickle_mode=False, legacy_theme_mode=False, lazy_mode=F
def clean_temporary_pub(): def clean_temporary_pub():
if get_publisher(): if get_publisher():
get_publisher().cleanup() get_publisher().cleanup()
if known_elements.legacy_theme_app_dir and os.path.exists(known_elements.legacy_theme_app_dir):
shutil.rmtree(known_elements.legacy_theme_app_dir)
known_elements.legacy_theme_app_dir = None
if known_elements.pickle_app_dir and os.path.exists(known_elements.pickle_app_dir): if known_elements.pickle_app_dir and os.path.exists(known_elements.pickle_app_dir):
shutil.rmtree(known_elements.pickle_app_dir) shutil.rmtree(known_elements.pickle_app_dir)
known_elements.pickle_app_dir = None known_elements.pickle_app_dir = None

View File

@ -24,11 +24,10 @@ try:
import lasso import lasso
except ImportError: except ImportError:
lasso = None lasso = None
import shutil
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
import zipfile import zipfile
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes
from quixote import get_publisher, get_request, get_response, get_session, redirect from quixote import get_publisher, get_request, get_response, get_session, redirect
from quixote.directory import Directory from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext from quixote.html import TemplateIO, htmltext
@ -41,7 +40,6 @@ from wcs.formdef import FormDef, FormdefImportError
from wcs.qommon import _, errors, get_cfg, ident, misc, template from wcs.qommon import _, errors, get_cfg, ident, misc, template
from wcs.qommon.admin.cfg import cfg_submit from wcs.qommon.admin.cfg import cfg_submit
from wcs.qommon.admin.emails import EmailsDirectory from wcs.qommon.admin.emails import EmailsDirectory
from wcs.qommon.admin.menu import error_page
from wcs.qommon.admin.settings import SettingsDirectory as QommonSettingsDirectory from wcs.qommon.admin.settings import SettingsDirectory as QommonSettingsDirectory
from wcs.qommon.admin.texts import TextsDirectory from wcs.qommon.admin.texts import TextsDirectory
from wcs.qommon.afterjobs import AfterJob from wcs.qommon.afterjobs import AfterJob
@ -59,7 +57,6 @@ from wcs.qommon.form import (
SingleSelectWidget, SingleSelectWidget,
StringWidget, StringWidget,
TextWidget, TextWidget,
UrlWidget,
WidgetList, WidgetList,
) )
from wcs.workflows import Workflow, WorkflowImportError from wcs.workflows import Workflow, WorkflowImportError
@ -449,51 +446,9 @@ class FileTypesDirectory(Directory):
return r.getvalue() return r.getvalue()
class ThemePreviewDirectory(Directory):
def _q_traverse(self, path):
if len(path) < 2:
return error_page('settings', _('Invalid URL'))
theme_id = path[0]
branding = get_publisher().cfg.get('branding', {})
original_branding = branding.copy()
get_publisher().cfg['branding'] = branding
get_publisher().cfg['branding']['theme'] = theme_id
if 'template' in get_publisher().cfg['branding']:
del get_publisher().cfg['branding']['template']
root_directory = get_publisher().root_directory_class()
response = get_response()
response.reset_includes()
response.filter = {}
del response.breadcrumb
if path[1] in ('backoffice', 'admin') or get_request().get_method() == 'POST':
from wcs.qommon.template import error_page as base_error_page
output = base_error_page(_("The theme preview doesn't support this."))
else:
output = root_directory._q_traverse(path[1:])
from wcs.qommon.template import decorate
if isinstance(output, template.QommonTemplateResponse):
output = template.render(output.templates, output.context)
theme_preview = decorate(output, response)
# restore original branding in case it has been changed
get_publisher().cfg['branding'] = original_branding
get_publisher().write_cfg()
response.filter['raw'] = True
return theme_preview
class SettingsDirectory(QommonSettingsDirectory): class SettingsDirectory(QommonSettingsDirectory):
_q_exports = [ _q_exports = [
'', '',
'themes',
'users', 'users',
'template', 'template',
'emails', 'emails',
@ -506,12 +461,9 @@ class SettingsDirectory(QommonSettingsDirectory):
'sms', 'sms',
'certificates', 'certificates',
'texts', 'texts',
'install_theme',
'download_theme',
'postgresql', 'postgresql',
('admin-permissions', 'admin_permissions'), ('admin-permissions', 'admin_permissions'),
'geolocation', 'geolocation',
'theme_preview',
'filetypes', 'filetypes',
('user-templates', 'user_templates'), ('user-templates', 'user_templates'),
('data-sources', 'data_sources'), ('data-sources', 'data_sources'),
@ -523,7 +475,6 @@ class SettingsDirectory(QommonSettingsDirectory):
identification = IdentificationDirectory() identification = IdentificationDirectory()
users = UsersDirectory() users = UsersDirectory()
texts = TextsDirectory() texts = TextsDirectory()
theme_preview = ThemePreviewDirectory()
filetypes = FileTypesDirectory() filetypes = FileTypesDirectory()
data_sources = NamedDataSourcesDirectory() data_sources = NamedDataSourcesDirectory()
wscalls = NamedWsCallsDirectory() wscalls = NamedWsCallsDirectory()
@ -636,13 +587,6 @@ class SettingsDirectory(QommonSettingsDirectory):
_('Language'), _('Language'),
_('Configure site language'), _('Configure site language'),
) )
if enabled('theme'):
r += htmltext('<dt><a href="themes">%s</a></dt> <dd>%s</dd>') % (_('Theme'), _('Configure theme'))
if enabled('template'):
r += htmltext('<dt><a href="template">%s</a></dt> <dd>%s</dd>') % (
_('Template'),
_('Configure template'),
)
if enabled('geolocation'): if enabled('geolocation'):
r += htmltext('<dt><a href="geolocation">%s</a></dt> <dd>%s</dd>') % ( r += htmltext('<dt><a href="geolocation">%s</a></dt> <dd>%s</dd>') % (
_('Geolocation'), _('Geolocation'),
@ -766,217 +710,6 @@ class SettingsDirectory(QommonSettingsDirectory):
get_publisher().write_cfg() get_publisher().write_cfg()
return redirect('.') return redirect('.')
def themes(self):
request = get_request()
if 'theme' not in request.form:
current_theme = get_cfg('branding', {}).get('theme', 'default')
get_response().breadcrumb.append(('themes', _('Themes')))
html_top('settings', title=_('Themes'))
r = TemplateIO(html=True)
r += htmltext("<h2>%s</h2>") % _('Themes')
r += get_session().display_message()
r += htmltext('<a rel="popup" href="install_theme">%s</a>') % _('Install New Theme')
r += htmltext('<form action="themes" enctype="multipart/form-data" method="post">')
themes = template.get_themes_dict()
r += htmltext('<ul class="biglist themes">')
for theme, theme_dict in sorted(themes.items()):
label = theme_dict.get('label')
if 'version' in theme_dict:
label = '%s (%s)' % (label, theme_dict.get('version'))
if current_theme == theme:
checked = ' checked="checked"'
else:
checked = ''
r += htmltext('<li>')
r += htmltext('<strong class="label"><label>')
r += htmltext(' <input name="theme" value="%s" type="radio"%s>%s</input>') % (
theme,
checked,
label,
)
r += htmltext('</label></strong>')
if theme_dict.get('icon'):
r += htmltext('<img src="/themes/%s/icon.png" alt="" class="theme-icon" />') % theme
r += htmltext('<p class="details">%s') % theme_dict.get('desc', '')
r += htmltext(' [<a href="download_theme?theme=%s">%s</a>]') % (theme, _('download'))
r += htmltext(' [<a class="theme-preview" href="theme_preview/%s/">%s</a>]') % (
theme,
_('preview'),
)
if theme_dict.get('author'):
r += htmltext('<br/>')
r += htmltext(_('by %s')) % theme_dict.get('author')
r += htmltext('</p>')
r += htmltext('</li>')
r += htmltext('</ul>')
r += htmltext('<div class="buttons">')
r += htmltext('<button>%s</button>') % _('Submit')
r += htmltext('</div>')
r += htmltext('</form>')
return r.getvalue()
else:
themes = template.get_themes()
if str(request.form['theme']) in themes:
branding_cfg = get_cfg('branding', {})
branding_cfg['theme'] = str(request.form['theme'])
get_publisher().cfg['branding'] = branding_cfg
get_publisher().write_cfg()
return redirect('.')
def download_theme(self):
theme_id = get_request().form.get('theme')
if not theme_id:
return redirect('themes')
theme_directory = template.get_theme_directory(theme_id)
if not theme_directory:
return redirect('themes')
parent_theme_directory = os.path.dirname(theme_directory)
c = io.BytesIO()
with zipfile.ZipFile(c, 'w') as z:
for base, dummy, filenames in os.walk(theme_directory):
basetheme = base[len(parent_theme_directory) + 1 :]
for filename in filenames:
z.write(os.path.join(base, filename), os.path.join(basetheme, filename))
response = get_response()
response.set_content_type('application/zip')
response.set_header('content-disposition', 'attachment; filename=%s.zip' % theme_id)
return c.getvalue()
def install_theme(self):
form = Form(enctype='multipart/form-data')
form.add(FileWidget, 'file', title=_('Theme File'), required=False)
form.add(UrlWidget, 'url', title=_('Theme Address'), required=False, size=50)
form.add_submit('submit', _('Install'))
form.add_submit('cancel', _('Cancel'))
if form.get_submit() == 'cancel':
return redirect('.')
if form.is_submitted() and not form.has_errors():
try:
return self.install_theme_submit(form)
except ValueError:
form.get_widget('file').set_error(_('Invalid Theme'))
get_response().breadcrumb.append(('install_theme', _('Install Theme')))
html_top('forms', title=_('Install Theme'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Install Theme')
r += htmltext('<p>%s</p>') % _(
'You can install a new theme by uploading a file or by pointing to the theme URL.'
)
r += form.render()
return r.getvalue()
def install_theme_submit(self, form):
if form.get_widget('url').parse():
return self.install_theme_from_url(form.get_widget('url').parse())
if form.get_widget('file').parse():
return self.install_theme_from_file(form.get_widget('file').parse().fp)
get_session().message = ('error', _('You have to enter a file or a URL.'))
return redirect('themes')
def install_theme_from_file(self, fp):
try:
with zipfile.ZipFile(fp, 'r') as z:
theme_dir = os.path.join(get_publisher().app_dir, 'themes')
filename_list = [x for x in z.namelist() if x[0] != '/' and x[-1] != '/']
if len(filename_list) == 0:
get_session().message = ('error', _('Empty theme file.'))
return redirect('themes')
theme_name = filename_list[0].split('/')[0]
if ('%s/desc.xml' % theme_name) not in filename_list:
get_session().message = ('error', _('Theme is missing a desc.xml file.'))
return redirect('themes')
desc_xml = z.read('%s/desc.xml' % theme_name)
theme_dict = template.get_theme_dict(io.StringIO(force_text(desc_xml)))
if theme_dict.get('name') != theme_name:
get_session().message = ('error', _('desc.xml is missing a name attribute.'))
return redirect('themes')
if os.path.exists(os.path.join(theme_dir, theme_name)):
shutil.rmtree(os.path.join(theme_dir, theme_name))
for f in z.namelist():
if f[-1] == '/':
continue
path = os.path.join(theme_dir, f)
data = z.read(f)
if not os.path.exists(os.path.dirname(path)):
os.makedirs(os.path.dirname(path))
with open(path, 'wb') as _f:
_f.write(data)
return redirect('themes')
except Exception as e:
get_session().message = ('error', _('Failed to read theme file. (%s)') % str(e))
return redirect('themes')
def install_theme_from_url(self, url):
try:
fp = misc.urlopen(url)
except misc.ConnectionError as e:
get_session().message = ('error', _('Error loading theme (%s).') % str(e))
return redirect('themes')
return self.install_theme_from_file(io.StringIO(fp.read()))
def template(self):
from wcs.qommon.template import get_default_ezt_template
default_template_ezt = get_default_ezt_template()
branding_cfg = get_cfg('branding', {})
template = branding_cfg.get('template', default_template_ezt)
form = Form(enctype="multipart/form-data")
form.add(TextWidget, 'template', title=_('Site Template'), value=template, cols=80, rows=25)
form.add_submit('submit', _('Submit'))
form.add_submit('restore-default', _('Restore default template'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if form.get_submit() == 'cancel':
return redirect('.')
if form.get_submit() == 'restore-default':
self.template_submit()
return redirect('.')
if form.is_submitted() and not form.has_errors():
self.template_submit(form)
return redirect('.')
get_response().breadcrumb.append(('template', _('Template')))
html_top('settings', title=_('Template'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Template')
r += form.render()
return r.getvalue()
def template_submit(self, form=None):
from wcs.qommon.template import DEFAULT_TEMPLATE_EZT, get_default_ezt_template
theme_default_template_ezt = get_default_ezt_template()
get_publisher().reload_cfg()
branding_cfg = get_cfg('branding', {})
if not form:
template = None
else:
template = form.get_widget('template').parse()
if template in (DEFAULT_TEMPLATE_EZT, theme_default_template_ezt) or not template:
if 'template' in branding_cfg:
del branding_cfg['template']
else:
branding_cfg['template'] = template
get_publisher().cfg['branding'] = branding_cfg
get_publisher().write_cfg()
def export(self): def export(self):
if get_request().form.get('download'): if get_request().form.get('download'):
return self.export_download() return self.export_download()

View File

@ -14,7 +14,6 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>. # along with this program; if not, see <http://www.gnu.org/licenses/>.
import os
from contextlib import contextmanager from contextlib import contextmanager
from threading import Lock from threading import Lock
@ -31,7 +30,7 @@ from quixote.http_request import Upload
from .publisher import WcsPublisher from .publisher import WcsPublisher
from .qommon import force_str, template from .qommon import force_str, template
from .qommon.http_request import HTTPRequest from .qommon.http_request import HTTPRequest
from .qommon.publisher import get_cfg, set_publisher_class from .qommon.publisher import set_publisher_class
class TemplateWithFallbackView(TemplateView): class TemplateWithFallbackView(TemplateView):
@ -65,8 +64,8 @@ class TemplateWithFallbackView(TemplateView):
elif request.headers.get('X-Popup') == 'true': elif request.headers.get('X-Popup') == 'true':
response = HttpResponse('<div class="popup-content">%s</div>' % context['body']) response = HttpResponse('<div class="popup-content">%s</div>' % context['body'])
elif 'raw' in (getattr(self.quixote_response, 'filter') or {}): elif 'raw' in (getattr(self.quixote_response, 'filter') or {}):
# used for theme preview (generated in /backoffice/ but cannot # used for raw HTML snippets (for example in the test tool
# obviously receive the admin template. # results in inspect page).
response = HttpResponse(context['body']) response = HttpResponse(context['body'])
else: else:
response = self.render_to_response(context) response = self.render_to_response(context)
@ -145,19 +144,6 @@ class CompatWcsPublisher(WcsPublisher):
return output return output
if request.headers.get('X-Popup') == 'true': if request.headers.get('X-Popup') == 'true':
return '<div class="popup-content">%s</div>' % output return '<div class="popup-content">%s</div>' % output
if response.filter and response.filter.get('admin_ezt'):
return self.render_response(output)
current_theme = get_cfg('branding', {}).get('theme', 'default')
theme_directory = template.get_theme_directory(current_theme)
if not theme_directory:
return self.render_response(output)
if not os.path.exists(os.path.join(theme_directory, 'templates')):
return self.render_response(output)
if not os.path.exists(os.path.join(theme_directory, 'templates/wcs/base.html')):
return self.render_response(output)
if isinstance(output, template.QommonTemplateResponse): if isinstance(output, template.QommonTemplateResponse):
template_response = output template_response = output

View File

@ -116,7 +116,7 @@ class QommonPublisher(Publisher):
after_login_url = '' after_login_url = ''
qommon_static_dir = 'static/' qommon_static_dir = 'static/'
qommon_admin_css = 'css/dc2/admin.css' qommon_admin_css = 'css/dc2/admin.css'
default_theme = 'default' default_theme = 'django'
site_options = None site_options = None
site_charset = 'utf-8' site_charset = 'utf-8'
@ -346,11 +346,6 @@ class QommonPublisher(Publisher):
return error_page return error_page
def render_response(self, content):
if isinstance(content, template.QommonTemplateResponse):
content = template.render(content.templates, content.context)
return template.decorate(content, self.get_request().response)
def install_lang(self, request=None): def install_lang(self, request=None):
if request: if request:
lang = request.language lang = request.language

View File

@ -14,11 +14,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>. # along with this program; if not, see <http://www.gnu.org/licenses/>.
import glob
import io import io
import os import os
import re import re
import xml.etree.ElementTree as ET
import django.template import django.template
from django.template import TemplateSyntaxError as DjangoTemplateSyntaxError from django.template import TemplateSyntaxError as DjangoTemplateSyntaxError
@ -27,9 +25,7 @@ from django.template import engines
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.encoding import force_text, smart_text from django.utils.encoding import force_text, smart_text
from quixote import get_publisher, get_request, get_response, get_session from quixote import get_publisher, get_request, get_response, get_session
from quixote.directory import Directory
from quixote.html import TemplateIO, htmlescape, htmltext from quixote.html import TemplateIO, htmlescape, htmltext
from quixote.util import StaticDirectory, StaticFile
from . import ezt, force_str from . import ezt, force_str
@ -53,123 +49,6 @@ def get_theme_directory(theme_id):
return location return location
class ThemesDirectory(Directory):
def _q_lookup(self, component):
from . import errors
if component in ('.', '..'):
raise errors.TraversalError()
location = get_theme_directory(component)
if location is None:
raise errors.TraversalError()
if os.path.isdir(location):
return StaticDirectory(location)
else:
return StaticFile(location)
def get_themes_dict():
system_location = os.path.join(get_publisher().data_dir, 'themes')
local_location = os.path.join(get_publisher().app_dir, 'themes')
themes = {}
for theme_xml in glob.glob(os.path.join(system_location, '*/desc.xml')) + glob.glob(
os.path.join(local_location, '*/desc.xml')
):
theme_dict = get_theme_dict(theme_xml)
if not theme_dict:
continue
themes[theme_dict.get('name')] = theme_dict
return themes
def get_theme_dict(theme_xml):
try:
tree = ET.parse(theme_xml).getroot()
except Exception: # parse error
return None
name = force_str(tree.attrib['name'])
version = force_str(tree.attrib.get('version') or '')
label = force_str(tree.findtext('label') or '')
desc = force_str(tree.findtext('desc') or '')
author = force_str(tree.findtext('author') or '')
icon = None
if isinstance(theme_xml, str):
icon = os.path.join(os.path.dirname(theme_xml), 'icon.png')
if not os.path.exists(icon):
icon = None
theme = {'name': name, 'label': label, 'desc': desc, 'author': author, 'icon': icon, 'version': version}
theme['keywords'] = []
for keyword in tree.findall('keywords/keyword'):
theme['keywords'].append(keyword.text)
return theme
def get_themes():
# backward compatibility function, it returns a tuple with theme info,
# newer code should use get_themes_dict()
themes = {}
for k, v in get_themes_dict().items():
themes[k] = (v['label'], v['desc'], v['author'], v['icon'])
return themes
def get_current_theme():
from .publisher import get_cfg
current_theme = get_cfg('branding', {}).get('theme', 'default')
system_location = os.path.join(get_publisher().data_dir, 'themes', current_theme)
local_location = os.path.join(get_publisher().app_dir, 'themes', current_theme)
for location in (local_location, system_location):
if os.path.exists(location):
return get_theme_dict(os.path.join(location, 'desc.xml'))
default_theme_location = os.path.join(get_publisher().data_dir, 'themes', 'default')
return get_theme_dict(os.path.join(default_theme_location, 'desc.xml'))
DEFAULT_TEMPLATE_EZT = """<!DOCTYPE html>
<html lang="[site_lang]">
<head>
<title>[page_title]</title>
<link rel="stylesheet" type="text/css" href="[css]"/>
[script]
</head>
<body[if-any onload] onload="[onload]"[end]>
<div id="page">
<div id="top"> <h1>[if-any title][title][else][site_name][end]</h1> </div>
<div id="main-content">
[if-any breadcrumb]<p id="breadcrumb">[breadcrumb]</p>[end]
[body]
</div>
<div id="footer">[if-any footer][footer][end]</div>
</div>
</body>
</html>"""
DEFAULT_IFRAME_EZT = """<!DOCTYPE html>
<html lang="[site_lang]">
<head>
<title>[page_title]</title>
<link rel="stylesheet" type="text/css" href="[css]"/>
[script]
</head>
<body[if-any onload] onload="[onload]"[end]>
<div id="main-content">
[if-any breadcrumb]<p id="breadcrumb">[breadcrumb]</p>[end]
[body]
</div>
</body>
</html>"""
default_template = ezt.Template()
default_template.parse(DEFAULT_TEMPLATE_EZT)
default_iframe_template = ezt.Template()
default_iframe_template.parse(DEFAULT_IFRAME_EZT)
def html_top(title=None, default_org=None): def html_top(title=None, default_org=None):
if not hasattr(get_response(), 'filter'): if not hasattr(get_response(), 'filter'):
get_response().filter = {} get_response().filter = {}
@ -207,38 +86,6 @@ def error_page(error_message, error_title=None, location_hint=None):
return htmltext(r.getvalue()) return htmltext(r.getvalue())
def get_default_ezt_template():
from .publisher import get_cfg
current_theme = get_cfg('branding', {}).get('theme', 'default')
filename = os.path.join(
get_publisher().app_dir, 'themes', current_theme, 'template.%s.ezt' % get_publisher().APP_NAME
)
if os.path.exists(filename):
with open(filename) as fd:
return fd.read()
filename = os.path.join(
get_publisher().data_dir, 'themes', current_theme, 'template.%s.ezt' % get_publisher().APP_NAME
)
if os.path.exists(filename):
with open(filename) as fd:
return fd.read()
filename = os.path.join(get_publisher().app_dir, 'themes', current_theme, 'template.ezt')
if os.path.exists(filename):
with open(filename) as fd:
return fd.read()
filename = os.path.join(get_publisher().data_dir, 'themes', current_theme, 'template.ezt')
if os.path.exists(filename):
with open(filename) as fd:
return fd.read()
return DEFAULT_TEMPLATE_EZT
def get_decorate_vars(body, response, generate_breadcrumb=True, **kwargs): def get_decorate_vars(body, response, generate_breadcrumb=True, **kwargs):
from .publisher import get_cfg from .publisher import get_cfg
@ -303,20 +150,10 @@ def get_decorate_vars(body, response, generate_breadcrumb=True, **kwargs):
subtitle = kwargs.get('subtitle') subtitle = kwargs.get('subtitle')
sidebar = kwargs.get('sidebar') sidebar = kwargs.get('sidebar')
css = root_url + get_publisher().qommon_static_dir + get_publisher().qommon_admin_css css = root_url + get_publisher().qommon_static_dir + get_publisher().qommon_admin_css
app_dir_filename = os.path.join(get_publisher().app_dir, 'themes', current_theme, 'admin.css')
data_dir_filename = os.path.join(get_publisher().data_dir, 'themes', current_theme, 'admin.css')
for filename in (app_dir_filename, data_dir_filename):
if os.path.exists(filename):
extra_css = root_url + 'themes/%s/admin.css' % current_theme
break
extra_head = get_publisher().get_site_option('backoffice_extra_head') extra_head = get_publisher().get_site_option('backoffice_extra_head')
app_label = get_publisher().get_site_option('app_label') or 'w.c.s.' app_label = get_publisher().get_site_option('app_label') or 'w.c.s.'
else: else:
if current_theme == 'default': css = root_url + 'themes/%s/%s.css' % (current_theme, get_publisher().APP_NAME)
css = root_url + 'static/css/%s.css' % get_publisher().APP_NAME
else:
css = root_url + 'themes/%s/%s.css' % (current_theme, get_publisher().APP_NAME)
# this variable is kept in locals() as it was once part of the default # this variable is kept in locals() as it was once part of the default
# template and existing installations may have template changes that # template and existing installations may have template changes that
@ -354,53 +191,6 @@ def get_decorate_vars(body, response, generate_breadcrumb=True, **kwargs):
return vars return vars
def decorate(body, response):
if get_request().get_header('x-popup') == 'true':
return '''<div class="popup-content"> %s </div>''' % body
from .publisher import get_cfg
generate_breadcrumb = True
template_ezt = get_cfg('branding', {}).get('template')
current_theme = get_cfg('branding', {}).get('theme', 'default')
if not template_ezt:
# the theme can provide a default template
possible_filenames = []
possible_filenames.append('template.%s.ezt' % get_publisher().APP_NAME)
possible_filenames.append('template.ezt')
possible_dirnames = [
os.path.join(get_publisher().app_dir, 'themes', current_theme),
os.path.join(get_publisher().data_dir, 'themes', current_theme),
os.path.join(get_publisher().data_dir, 'themes', 'default'),
]
for fname in possible_filenames:
for dname in possible_dirnames:
filename = os.path.join(dname, fname)
if os.path.exists(filename):
with open(filename) as fd:
template_ezt = fd.read()
break
else:
continue
break
if template_ezt:
generate_breadcrumb = '[breadcrumb]' in template_ezt
template = ezt.Template()
template.parse(template_ezt)
else:
template = default_template
fd = io.StringIO()
vars = get_decorate_vars(body, response, generate_breadcrumb=generate_breadcrumb)
template.generate(fd, vars)
return fd.getvalue()
def render(template_name, context): def render(template_name, context):
request = getattr(get_request(), 'django_request', None) request = getattr(get_request(), 'django_request', None)
result = render_to_string(template_name, context, request=request) result = render_to_string(template_name, context, request=request)

View File

@ -265,7 +265,6 @@ class RootDirectory(Directory):
] ]
api = ApiDirectory() api = ApiDirectory()
themes = template.ThemesDirectory()
myspace = MyspaceDirectory() myspace = MyspaceDirectory()
pages = PagesDirectory() pages = PagesDirectory()
fargo = portfolio.FargoDirectory() fargo = portfolio.FargoDirectory()

View File

@ -38,7 +38,7 @@ class TemplateLoader(django.template.loaders.filesystem.Loader):
template_dirs.append(os.path.join(get_publisher().app_dir, 'templates')) template_dirs.append(os.path.join(get_publisher().app_dir, 'templates'))
template_dirs.append(os.path.join(get_publisher().app_dir, 'theme', 'templates')) template_dirs.append(os.path.join(get_publisher().app_dir, 'theme', 'templates'))
current_theme = get_cfg('branding', {}).get('theme', 'default') current_theme = get_cfg('branding', {}).get('theme', get_publisher().default_theme)
theme_directory = get_theme_directory(current_theme) theme_directory = get_theme_directory(current_theme)
if theme_directory: if theme_directory:
# templates from theme directory # templates from theme directory