From b62e39d7e405de491ddc48eac21a2e6cbdd77a90 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= <fpeters@entrouvert.com>
Date: Tue, 11 Oct 2022 17:26:52 +0200
Subject: [PATCH] general: remove legacy theming code (#70128)

---
 MANIFEST.in                                |   2 -
 data/themes/alto/desc.xml                  |   6 -
 data/themes/alto/icon.png                  | Bin 728 -> 0 bytes
 data/themes/alto/img/bottom.jpg            | Bin 10161 -> 0 bytes
 data/themes/alto/img/page.jpg              | Bin 508 -> 0 bytes
 data/themes/alto/img/top.jpg               | Bin 10248 -> 0 bytes
 data/themes/alto/template.ezt              |  18 --
 data/themes/alto/wcs.css                   | 270 ---------------------
 data/themes/default/desc.xml               |   6 -
 data/themes/default/icon.png               | Bin 675 -> 0 bytes
 data/themes/default/mobile.css             |   0
 data/themes/django/templates/wcs/base.html |   2 +-
 tests/admin_pages/test_settings.py         | 122 +---------
 tests/form_pages/test_all.py               |  22 --
 tests/test_hobo.py                         |   6 +-
 tests/utilities.py                         |  22 +-
 wcs/admin/settings.py                      | 269 +-------------------
 wcs/compat.py                              |  20 +-
 wcs/qommon/publisher.py                    |   7 +-
 wcs/qommon/template.py                     | 212 +---------------
 wcs/root.py                                |   1 -
 wcs/utils.py                               |   2 +-
 22 files changed, 16 insertions(+), 971 deletions(-)
 delete mode 100644 data/themes/alto/desc.xml
 delete mode 100644 data/themes/alto/icon.png
 delete mode 100644 data/themes/alto/img/bottom.jpg
 delete mode 100644 data/themes/alto/img/page.jpg
 delete mode 100644 data/themes/alto/img/top.jpg
 delete mode 100644 data/themes/alto/template.ezt
 delete mode 100644 data/themes/alto/wcs.css
 delete mode 100644 data/themes/default/desc.xml
 delete mode 100644 data/themes/default/icon.png
 delete mode 100644 data/themes/default/mobile.css

diff --git a/MANIFEST.in b/MANIFEST.in
index 71675dd95..f6497fdd9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -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
diff --git a/data/themes/alto/desc.xml b/data/themes/alto/desc.xml
deleted file mode 100644
index d04d745a7..000000000
--- a/data/themes/alto/desc.xml
+++ /dev/null
@@ -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>
diff --git a/data/themes/alto/icon.png b/data/themes/alto/icon.png
deleted file mode 100644
index 4276e319e86c894b47d761319150994e302fff81..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/data/themes/alto/img/bottom.jpg b/data/themes/alto/img/bottom.jpg
deleted file mode 100644
index f01a9db42c01f900e8896eeb4d58c35252e9a03a..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/data/themes/alto/img/page.jpg b/data/themes/alto/img/page.jpg
deleted file mode 100644
index 47413f7bf17e7218a3a91f15ab00cf181904acf6..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/data/themes/alto/img/top.jpg b/data/themes/alto/img/top.jpg
deleted file mode 100644
index fc2d41a5542b2446d6d05d3991e770ae3dc0ad5b..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/data/themes/alto/template.ezt b/data/themes/alto/template.ezt
deleted file mode 100644
index aca3e9009..000000000
--- a/data/themes/alto/template.ezt
+++ /dev/null
@@ -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>
diff --git a/data/themes/alto/wcs.css b/data/themes/alto/wcs.css
deleted file mode 100644
index 00188ae05..000000000
--- a/data/themes/alto/wcs.css
+++ /dev/null
@@ -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;
-}
-
diff --git a/data/themes/default/desc.xml b/data/themes/default/desc.xml
deleted file mode 100644
index 1c4571a8e..000000000
--- a/data/themes/default/desc.xml
+++ /dev/null
@@ -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>
diff --git a/data/themes/default/icon.png b/data/themes/default/icon.png
deleted file mode 100644
index f6838d2501f4ba5533c4c90323f2faa7e5d3eaaf..0000000000000000000000000000000000000000
GIT binary patch
diff --git a/data/themes/default/mobile.css b/data/themes/default/mobile.css
deleted file mode 100644
index e69de29bb..000000000
diff --git a/data/themes/django/templates/wcs/base.html b/data/themes/django/templates/wcs/base.html
index 243ff32b6..2b3db1b4e 100644
--- a/data/themes/django/templates/wcs/base.html
+++ b/data/themes/django/templates/wcs/base.html
@@ -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">
diff --git a/tests/admin_pages/test_settings.py b/tests/admin_pages/test_settings.py
index cf002a655..801094b3c 100644
--- a/tests/admin_pages/test_settings.py
+++ b/tests/admin_pages/test_settings.py
@@ -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)
 
diff --git a/tests/form_pages/test_all.py b/tests/form_pages/test_all.py
index b94a479f4..05d6facc9 100644
--- a/tests/form_pages/test_all.py
+++ b/tests/form_pages/test_all.py
@@ -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)
diff --git a/tests/test_hobo.py b/tests/test_hobo.py
index a5157f308..91895c0aa 100644
--- a/tests/test_hobo.py
+++ b/tests/test_hobo.py
@@ -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)
diff --git a/tests/utilities.py b/tests/utilities.py
index 3544b6c0d..21d9a55c7 100644
--- a/tests/utilities.py
+++ b/tests/utilities.py
@@ -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
diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py
index 709fa4ff6..b414c4c83 100644
--- a/wcs/admin/settings.py
+++ b/wcs/admin/settings.py
@@ -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()
diff --git a/wcs/compat.py b/wcs/compat.py
index 2ced96fea..3dd646764 100644
--- a/wcs/compat.py
+++ b/wcs/compat.py
@@ -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
diff --git a/wcs/qommon/publisher.py b/wcs/qommon/publisher.py
index ed7be5e22..149fca327 100644
--- a/wcs/qommon/publisher.py
+++ b/wcs/qommon/publisher.py
@@ -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
diff --git a/wcs/qommon/template.py b/wcs/qommon/template.py
index 8e4f33a6b..5604fbfee 100644
--- a/wcs/qommon/template.py
+++ b/wcs/qommon/template.py
@@ -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)
diff --git a/wcs/root.py b/wcs/root.py
index 35ae5eaa9..94c662fd9 100644
--- a/wcs/root.py
+++ b/wcs/root.py
@@ -265,7 +265,6 @@ class RootDirectory(Directory):
     ]
 
     api = ApiDirectory()
-    themes = template.ThemesDirectory()
     myspace = MyspaceDirectory()
     pages = PagesDirectory()
     fargo = portfolio.FargoDirectory()
diff --git a/wcs/utils.py b/wcs/utils.py
index 8bb0822f0..280e6d429 100644
--- a/wcs/utils.py
+++ b/wcs/utils.py
@@ -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
-- 
2.37.2

