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 extra/ *.py
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 wcs/qommon/static/ *.css *.scss *.png *.gif *.jpg *.js *.eot *.svg *.ttf *.woff *.map
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="top">
{% block header %}
<h1>WIP/DJANGO - {% if title %}{{ title }}{% else %}{{ site_name }}{% endif %}</h1>
<h1>{% if title %}{{ title }}{% else %}{{ site_name }}{% endif %}</h1>
{% endblock %}
</div>
<div id="main-content">

View File

@ -28,7 +28,6 @@ from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.qommon.form import UploadedFile
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.workflows import Workflow
from wcs.wscalls import NamedWsCall
@ -74,16 +73,14 @@ def test_settings_disabled_screens(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/settings/')
assert 'Identification' in resp.text
assert 'Theme' in resp.text
if not pub.site_options.has_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:
pub.site_options.write(fd)
resp = app.get('/backoffice/settings/')
assert 'Identification' not in resp.text
assert 'Theme' not in resp.text
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
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):
user = create_superuser(pub)
app = login(get_app(pub))
@ -829,68 +771,6 @@ def test_settings_permissions(pub):
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):
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/'
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):
formdef = create_formdef()
get_app(pub).get('/test/', status=200)

View File

@ -314,14 +314,14 @@ def test_update_configuration(setuptest):
def test_update_themes(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]
hobo_cmd.update_configuration(service, pub)
assert pub.cfg['branding']['theme'] == 'default'
assert pub.cfg['branding']['theme'] == 'django'
service['variables']['theme'] = 'foobar'
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.update_configuration(service, pub)

View File

@ -33,30 +33,25 @@ class KnownElements:
pickle_app_dir = None
sql_app_dir = None
sql_db_name = None
legacy_theme_app_dir = None
lazy_app_dir = None
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():
get_publisher().cleanup()
cleanup()
if legacy_theme_mode and known_elements.legacy_theme_app_dir:
APP_DIR = known_elements.legacy_theme_app_dir
elif lazy_mode and known_elements.lazy_app_dir:
if lazy_mode and known_elements.lazy_app_dir:
APP_DIR = known_elements.lazy_app_dir
elif pickle_mode and 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
else:
APP_DIR = tempfile.mkdtemp()
if legacy_theme_mode:
known_elements.legacy_theme_app_dir = APP_DIR
elif lazy_mode:
if lazy_mode:
known_elements.lazy_app_dir = APP_DIR
elif pickle_mode:
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',
}
pub.cfg['language'] = {'language': 'en'}
if legacy_theme_mode:
pub.cfg['branding'] = {'theme': 'default'}
else:
pub.cfg['branding'] = {'theme': 'django'}
pub.write_cfg()
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():
if get_publisher():
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):
shutil.rmtree(known_elements.pickle_app_dir)
known_elements.pickle_app_dir = None

View File

@ -24,11 +24,10 @@ try:
import lasso
except ImportError:
lasso = None
import shutil
import xml.etree.ElementTree as ET
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.directory import Directory
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.admin.cfg import cfg_submit
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.texts import TextsDirectory
from wcs.qommon.afterjobs import AfterJob
@ -59,7 +57,6 @@ from wcs.qommon.form import (
SingleSelectWidget,
StringWidget,
TextWidget,
UrlWidget,
WidgetList,
)
from wcs.workflows import Workflow, WorkflowImportError
@ -449,51 +446,9 @@ class FileTypesDirectory(Directory):
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):
_q_exports = [
'',
'themes',
'users',
'template',
'emails',
@ -506,12 +461,9 @@ class SettingsDirectory(QommonSettingsDirectory):
'sms',
'certificates',
'texts',
'install_theme',
'download_theme',
'postgresql',
('admin-permissions', 'admin_permissions'),
'geolocation',
'theme_preview',
'filetypes',
('user-templates', 'user_templates'),
('data-sources', 'data_sources'),
@ -523,7 +475,6 @@ class SettingsDirectory(QommonSettingsDirectory):
identification = IdentificationDirectory()
users = UsersDirectory()
texts = TextsDirectory()
theme_preview = ThemePreviewDirectory()
filetypes = FileTypesDirectory()
data_sources = NamedDataSourcesDirectory()
wscalls = NamedWsCallsDirectory()
@ -636,13 +587,6 @@ class SettingsDirectory(QommonSettingsDirectory):
_('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'):
r += htmltext('<dt><a href="geolocation">%s</a></dt> <dd>%s</dd>') % (
_('Geolocation'),
@ -766,217 +710,6 @@ class SettingsDirectory(QommonSettingsDirectory):
get_publisher().write_cfg()
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):
if get_request().form.get('download'):
return self.export_download()

View File

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

View File

@ -116,7 +116,7 @@ class QommonPublisher(Publisher):
after_login_url = ''
qommon_static_dir = 'static/'
qommon_admin_css = 'css/dc2/admin.css'
default_theme = 'default'
default_theme = 'django'
site_options = None
site_charset = 'utf-8'
@ -346,11 +346,6 @@ class QommonPublisher(Publisher):
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):
if request:
lang = request.language

View File

@ -14,11 +14,9 @@
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
import glob
import io
import os
import re
import xml.etree.ElementTree as ET
import django.template
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.utils.encoding import force_text, smart_text
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.util import StaticDirectory, StaticFile
from . import ezt, force_str
@ -53,123 +49,6 @@ def get_theme_directory(theme_id):
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):
if not hasattr(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())
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):
from .publisher import get_cfg
@ -303,20 +150,10 @@ def get_decorate_vars(body, response, generate_breadcrumb=True, **kwargs):
subtitle = kwargs.get('subtitle')
sidebar = kwargs.get('sidebar')
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')
app_label = get_publisher().get_site_option('app_label') or 'w.c.s.'
else:
if current_theme == 'default':
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)
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
# 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
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):
request = getattr(get_request(), 'django_request', None)
result = render_to_string(template_name, context, request=request)

View File

@ -265,7 +265,6 @@ class RootDirectory(Directory):
]
api = ApiDirectory()
themes = template.ThemesDirectory()
myspace = MyspaceDirectory()
pages = PagesDirectory()
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, '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)
if theme_directory:
# templates from theme directory