diff --git a/cms/envs/aws.py b/cms/envs/aws.py
index a1e21b6075..cc45b632e1 100644
--- a/cms/envs/aws.py
+++ b/cms/envs/aws.py
@@ -381,7 +381,6 @@ MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = ENV_TOKENS.get(
"MICROSITE_DATABASE_TEMPLATE_CACHE_TTL", MICROSITE_DATABASE_TEMPLATE_CACHE_TTL
)
-FOOTER_CACHE_TIMEOUT = ENV_TOKENS.get('FOOTER_CACHE_TIMEOUT', FOOTER_CACHE_TIMEOUT)
############################ OAUTH2 Provider ###################################
# OpenID Connect issuer ID. Normally the URL of the authentication endpoint.
diff --git a/cms/envs/bok_choy.py b/cms/envs/bok_choy.py
index da12fb1b29..15bd0dee05 100644
--- a/cms/envs/bok_choy.py
+++ b/cms/envs/bok_choy.py
@@ -59,9 +59,9 @@ STATIC_URL = "/static/"
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
)
-STATICFILES_DIRS = [
+STATICFILES_DIRS = (
(TEST_ROOT / "staticfiles" / "cms").abspath(),
-]
+)
# Silence noisy logs
import logging
diff --git a/cms/envs/common.py b/cms/envs/common.py
index 0d7faa6e3a..9c2112ca75 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -441,6 +441,7 @@ SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
# Site info
+SITE_ID = 1
SITE_NAME = "localhost:8001"
HTTPS = 'on'
ROOT_URLCONF = 'cms.urls'
@@ -512,7 +513,6 @@ STATICFILES_STORAGE = 'openedx.core.storage.ProductionStorage'
# List of finder classes that know how to find static files in various locations.
# Note: the pipeline finder is included to be able to discover optimized files
STATICFILES_FINDERS = [
- 'openedx.core.djangoapps.theming.finders.ComprehensiveThemeFinder',
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'pipeline.finders.PipelineFinder',
@@ -1128,10 +1128,6 @@ MICROSITE_TEMPLATE_BACKEND = 'microsite_configuration.backends.filebased.Filebas
# TTL for microsite database template cache
MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = 5 * 60
-# Cache expiration for the version of the footer served
-# by the branding API.
-FOOTER_CACHE_TIMEOUT = 30 * 60
-
############################### PROCTORING CONFIGURATION DEFAULTS ##############
PROCTORING_BACKEND_PROVIDER = {
'class': 'edx_proctoring.backends.null.NullBackendProvider',
diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py
index 044a997cea..64d417be63 100644
--- a/cms/envs/devstack.py
+++ b/cms/envs/devstack.py
@@ -41,7 +41,6 @@ STATICFILES_STORAGE = 'openedx.core.storage.DevelopmentStorage'
# Revert to the default set of finders as we don't want the production pipeline
STATICFILES_FINDERS = [
- 'openedx.core.djangoapps.theming.finders.ComprehensiveThemeFinder',
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]
diff --git a/cms/envs/devstack_optimized.py b/cms/envs/devstack_optimized.py
index b737704d38..2eaa3da79f 100644
--- a/cms/envs/devstack_optimized.py
+++ b/cms/envs/devstack_optimized.py
@@ -38,6 +38,6 @@ STATIC_URL = "/static/"
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
)
-STATICFILES_DIRS = [
+STATICFILES_DIRS = (
(TEST_ROOT / "staticfiles" / "cms").abspath(),
-]
+)
diff --git a/cms/envs/test.py b/cms/envs/test.py
index 8f2338e591..ce63bc4d90 100644
--- a/cms/envs/test.py
+++ b/cms/envs/test.py
@@ -30,7 +30,6 @@ from util.db import NoOpMigrationModules
from lms.envs.test import (
WIKI_ENABLED,
PLATFORM_NAME,
- SITE_ID,
SITE_NAME,
DEFAULT_FILE_STORAGE,
MEDIA_ROOT,
@@ -282,8 +281,6 @@ MICROSITE_CONFIGURATION = {
MICROSITE_TEST_HOSTNAME = 'testmicrosite.testserver'
MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver'
-TEST_THEME = COMMON_ROOT / "test" / "test-theme"
-
# For consistency in user-experience, keep the value of this setting in sync with
# the one in lms/envs/test.py
FEATURES['ENABLE_DISCUSSION_SERVICE'] = False
diff --git a/cms/startup.py b/cms/startup.py
index 8bc45723c5..6f64dac161 100644
--- a/cms/startup.py
+++ b/cms/startup.py
@@ -17,7 +17,7 @@ from monkey_patch import (
import xmodule.x_module
import cms.lib.xblock.runtime
-from openedx.core.djangoapps.theming.core import enable_comprehensive_theming
+from openedx.core.djangoapps.theming.core import enable_comprehensive_theme
def run():
@@ -30,7 +30,7 @@ def run():
# Comprehensive theming needs to be set up before django startup,
# because modifying django template paths after startup has no effect.
if settings.COMPREHENSIVE_THEME_DIR:
- enable_comprehensive_theming(settings.COMPREHENSIVE_THEME_DIR)
+ enable_comprehensive_theme(settings.COMPREHENSIVE_THEME_DIR)
django.setup()
diff --git a/cms/static/sass/partials/_variables.scss b/cms/static/sass/_variables.scss
similarity index 100%
rename from cms/static/sass/partials/_variables.scss
rename to cms/static/sass/_variables.scss
diff --git a/common/djangoapps/course_modes/tests/test_views.py b/common/djangoapps/course_modes/tests/test_views.py
index 844c82c8f2..60196cd940 100644
--- a/common/djangoapps/course_modes/tests/test_views.py
+++ b/common/djangoapps/course_modes/tests/test_views.py
@@ -22,7 +22,7 @@ from student.tests.factories import CourseEnrollmentFactory, UserFactory
from student.models import CourseEnrollment
import lms.djangoapps.commerce.tests.test_utils as ecomm_test_utils
from course_modes.models import CourseMode, Mode
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme
+from openedx.core.djangoapps.theming.test_util import with_is_edx_domain
@ddt.ddt
@@ -352,7 +352,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
self.assertEquals(course_modes, expected_modes)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
- @with_comprehensive_theme("edx.org")
+ @with_is_edx_domain(True)
def test_hide_nav(self):
# Create the course modes
for mode in ["honor", "verified"]:
diff --git a/common/djangoapps/edxmako/paths.py b/common/djangoapps/edxmako/paths.py
index ce0cb81b92..3e7bb40430 100644
--- a/common/djangoapps/edxmako/paths.py
+++ b/common/djangoapps/edxmako/paths.py
@@ -9,14 +9,9 @@ import pkg_resources
from django.conf import settings
from mako.lookup import TemplateLookup
-from mako.exceptions import TopLevelLookupException
+from microsite_configuration import microsite
from . import LOOKUP
-from openedx.core.djangoapps.theming.helpers import (
- get_template as themed_template,
- get_template_path_with_theme,
- strip_site_theme_templates_path,
-)
class DynamicTemplateLookup(TemplateLookup):
@@ -54,25 +49,15 @@ class DynamicTemplateLookup(TemplateLookup):
def get_template(self, uri):
"""
- Overridden method for locating a template in either the database or the site theme.
-
- If not found, template lookup will be done in comprehensive theme for current site
- by prefixing path to theme.
- e.g if uri is `main.html` then new uri would be something like this `/red-theme/lms/static/main.html`
-
- If still unable to find a template, it will fallback to the default template directories after stripping off
- the prefix path to theme.
+ Overridden method which will hand-off the template lookup to the microsite subsystem
"""
- template = themed_template(uri)
+ microsite_template = microsite.get_template(uri)
- if not template:
- try:
- template = super(DynamicTemplateLookup, self).get_template(get_template_path_with_theme(uri))
- except TopLevelLookupException:
- # strip off the prefix path to theme and look in default template dirs
- template = super(DynamicTemplateLookup, self).get_template(strip_site_theme_templates_path(uri))
-
- return template
+ return (
+ microsite_template
+ if microsite_template
+ else super(DynamicTemplateLookup, self).get_template(uri)
+ )
def clear_lookups(namespace):
diff --git a/common/djangoapps/edxmako/shortcuts.py b/common/djangoapps/edxmako/shortcuts.py
index 895420e32b..da5ddfe3cd 100644
--- a/common/djangoapps/edxmako/shortcuts.py
+++ b/common/djangoapps/edxmako/shortcuts.py
@@ -12,18 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
-
-from django.conf import settings
-from django.core.urlresolvers import reverse
-from django.http import HttpResponse
from django.template import Context
+from django.http import HttpResponse
+import logging
from microsite_configuration import microsite
from edxmako import lookup_template
from edxmako.middleware import get_template_request_context
-from openedx.core.djangoapps.theming.helpers import get_template_path
+from django.conf import settings
+from django.core.urlresolvers import reverse
log = logging.getLogger(__name__)
@@ -115,7 +113,8 @@ def microsite_footer_context_processor(request):
def render_to_string(template_name, dictionary, context=None, namespace='main'):
- template_name = get_template_path(template_name)
+ # see if there is an override template defined in the microsite
+ template_name = microsite.get_template_path(template_name)
context_instance = Context(dictionary)
# add dictionary to context_instance
diff --git a/common/djangoapps/microsite_configuration/backends/base.py b/common/djangoapps/microsite_configuration/backends/base.py
index 43743ee935..a8fec36caa 100644
--- a/common/djangoapps/microsite_configuration/backends/base.py
+++ b/common/djangoapps/microsite_configuration/backends/base.py
@@ -11,6 +11,7 @@ BaseMicrositeTemplateBackend is Base Class for the microsite template backend.
from __future__ import absolute_import
import abc
+import edxmako
import os.path
import threading
@@ -271,7 +272,9 @@ class BaseMicrositeBackend(AbstractBaseMicrositeBackend):
Configure the paths for the microsites feature
"""
microsites_root = settings.MICROSITE_ROOT_DIR
+
if os.path.isdir(microsites_root):
+ edxmako.paths.add_lookup('main', microsites_root)
settings.STATICFILES_DIRS.insert(0, microsites_root)
log.info('Loading microsite path at %s', microsites_root)
@@ -289,7 +292,6 @@ class BaseMicrositeBackend(AbstractBaseMicrositeBackend):
microsites_root = settings.MICROSITE_ROOT_DIR
if self.has_configuration_set():
- settings.MAKO_TEMPLATES['main'].insert(0, microsites_root)
settings.DEFAULT_TEMPLATE_ENGINE['DIRS'].append(microsites_root)
diff --git a/common/djangoapps/microsite_configuration/tests/backends/test_database.py b/common/djangoapps/microsite_configuration/tests/backends/test_database.py
index d643dfe695..43a96cf19d 100644
--- a/common/djangoapps/microsite_configuration/tests/backends/test_database.py
+++ b/common/djangoapps/microsite_configuration/tests/backends/test_database.py
@@ -105,23 +105,6 @@ class DatabaseMicrositeBackendTests(DatabaseMicrositeTestCase):
microsite.clear()
self.assertIsNone(microsite.get_value('platform_name'))
- def test_enable_microsites_pre_startup(self):
- """
- Tests microsite.test_enable_microsites_pre_startup works as expected.
- """
- # remove microsite root directory paths first
- settings.DEFAULT_TEMPLATE_ENGINE['DIRS'] = [
- path for path in settings.DEFAULT_TEMPLATE_ENGINE['DIRS']
- if path != settings.MICROSITE_ROOT_DIR
- ]
- with patch.dict('django.conf.settings.FEATURES', {'USE_MICROSITES': False}):
- microsite.enable_microsites_pre_startup(log)
- self.assertNotIn(settings.MICROSITE_ROOT_DIR, settings.DEFAULT_TEMPLATE_ENGINE['DIRS'])
- with patch.dict('django.conf.settings.FEATURES', {'USE_MICROSITES': True}):
- microsite.enable_microsites_pre_startup(log)
- self.assertIn(settings.MICROSITE_ROOT_DIR, settings.DEFAULT_TEMPLATE_ENGINE['DIRS'])
- self.assertIn(settings.MICROSITE_ROOT_DIR, settings.MAKO_TEMPLATES['main'])
-
@patch('edxmako.paths.add_lookup')
def test_enable_microsites(self, add_lookup):
"""
@@ -139,6 +122,7 @@ class DatabaseMicrositeBackendTests(DatabaseMicrositeTestCase):
with patch.dict('django.conf.settings.FEATURES', {'USE_MICROSITES': True}):
microsite.enable_microsites(log)
self.assertIn(settings.MICROSITE_ROOT_DIR, settings.STATICFILES_DIRS)
+ add_lookup.assert_called_once_with('main', settings.MICROSITE_ROOT_DIR)
def test_get_all_configs(self):
"""
diff --git a/common/djangoapps/pipeline_mako/templates/static_content.html b/common/djangoapps/pipeline_mako/templates/static_content.html
index 9a2754f892..20e527f5c9 100644
--- a/common/djangoapps/pipeline_mako/templates/static_content.html
+++ b/common/djangoapps/pipeline_mako/templates/static_content.html
@@ -4,13 +4,7 @@ from pipeline_mako import compressed_css, compressed_js
from django.utils.translation import get_language_bidi
from mako.exceptions import TemplateLookupException
-from openedx.core.djangoapps.theming.helpers import (
- get_page_title_breadcrumbs,
- get_value,
- get_template_path,
- get_themed_template_path,
- is_request_in_themed_site,
-)
+from openedx.core.djangoapps.theming.helpers import get_page_title_breadcrumbs, get_value, get_template_path, get_themed_template_path, is_request_in_themed_site
from certificates.api import get_asset_url_by_slug
from lang_pref.api import released_languages
%>
diff --git a/common/djangoapps/student/tests/test_email.py b/common/djangoapps/student/tests/test_email.py
index 6a26d3cfbd..84b3adb20c 100644
--- a/common/djangoapps/student/tests/test_email.py
+++ b/common/djangoapps/student/tests/test_email.py
@@ -22,7 +22,7 @@ from edxmako.shortcuts import render_to_string
from edxmako.tests import mako_middleware_process_request
from util.request import safe_get_host
from util.testing import EventTestMixin
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme
+from openedx.core.djangoapps.theming.test_util import with_is_edx_domain
class TestException(Exception):
@@ -100,7 +100,7 @@ class ActivationEmailTests(TestCase):
self._create_account()
self._assert_activation_email(self.ACTIVATION_SUBJECT, self.OPENEDX_FRAGMENTS)
- @with_comprehensive_theme("edx.org")
+ @with_is_edx_domain(True)
def test_activation_email_edx_domain(self):
self._create_account()
self._assert_activation_email(self.ACTIVATION_SUBJECT, self.EDX_DOMAIN_FRAGMENTS)
diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py
index 1da7d27d4e..0566460dd1 100644
--- a/common/djangoapps/student/tests/tests.py
+++ b/common/djangoapps/student/tests/tests.py
@@ -44,6 +44,7 @@ from certificates.tests.factories import GeneratedCertificateFactory # pylint:
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
import shoppingcart # pylint: disable=import-error
from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin
+from openedx.core.djangoapps.theming.test_util import with_is_edx_domain
# Explicitly import the cache from ConfigurationModel so we can reset it after each test
from config_models.models import cache
@@ -491,6 +492,7 @@ class DashboardTest(ModuleStoreTestCase):
self.assertEquals(response_2.status_code, 200)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ @with_is_edx_domain(True)
def test_dashboard_header_nav_has_find_courses(self):
self.client.login(username="jack", password="test")
response = self.client.get(reverse("dashboard"))
diff --git a/common/test/db_fixtures/sites.json b/common/test/db_fixtures/sites.json
deleted file mode 100644
index 5a7e8bc11b..0000000000
--- a/common/test/db_fixtures/sites.json
+++ /dev/null
@@ -1,20 +0,0 @@
-[
- {
- "pk": 2,
- "model": "sites.Site",
-
- "fields": {
- "domain": "localhost:8003",
- "name": "lms"
- }
- },
- {
- "pk": 3,
- "model": "sites.Site",
-
- "fields": {
- "domain": "localhost:8031",
- "name": "cms"
- }
- }
-]
diff --git a/common/test/test-theme/cms/static/css/.gitignore b/common/test/test-theme/cms/static/css/.gitignore
deleted file mode 100644
index b3a5267117..0000000000
--- a/common/test/test-theme/cms/static/css/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.css
diff --git a/common/test/test-theme/cms/static/sass/partials/_variables.scss b/common/test/test-theme/cms/static/sass/partials/_variables.scss
deleted file mode 100644
index 4265a16fec..0000000000
--- a/common/test/test-theme/cms/static/sass/partials/_variables.scss
+++ /dev/null
@@ -1,255 +0,0 @@
-// studio - utilities - variables
-// ====================
-
-// Table of Contents
-// * +Paths
-// * +Grid
-// * +Fonts
-// * +Colors - Utility
-// * +Colors - Primary
-// * +Colors - Shadow
-// * +Color - Application
-// * +Timing
-// * +Archetype UI
-// * +Specific UI
-// * +Deprecated
-
-$baseline: 20px;
-
-// +Paths
-// ====================
-$static-path: '..' !default;
-
-// +Grid
-// ====================
-$gw-column: ($baseline*3);
-$gw-gutter: $baseline;
-$fg-column: $gw-column;
-$fg-gutter: $gw-gutter;
-$fg-max-columns: 12;
-$fg-max-width: 1280px;
-$fg-min-width: 900px;
-
-// +Fonts
-// ====================
-$f-sans-serif: 'Open Sans','Helvetica Neue', Helvetica, Arial, sans-serif;
-$f-monospace: 'Bitstream Vera Sans Mono', Consolas, Courier, monospace;
-
-// +Colors - Utility
-// ====================
-$transparent: rgba(0,0,0,0); // used when color value is needed for UI width/transitions but element is transparent
-
-// +Colors - Primary
-// ====================
-$black: rgb(0,0,0);
-$black-t0: rgba($black, 0.125);
-$black-t1: rgba($black, 0.25);
-$black-t2: rgba($black, 0.5);
-$black-t3: rgba($black, 0.75);
-
-$white: rgb(255,255,255);
-$white-t0: rgba($white, 0.125);
-$white-t1: rgba($white, 0.25);
-$white-t2: rgba($white, 0.5);
-$white-t3: rgba($white, 0.75);
-
-$gray: rgb(127,127,127);
-$gray-l1: tint($gray,20%);
-$gray-l2: tint($gray,40%);
-$gray-l3: tint($gray,60%);
-$gray-l4: tint($gray,80%);
-$gray-l5: tint($gray,90%);
-$gray-l6: tint($gray,95%);
-$gray-l7: tint($gray,99%);
-$gray-d1: shade($gray,20%);
-$gray-d2: shade($gray,40%);
-$gray-d3: shade($gray,60%);
-$gray-d4: shade($gray,80%);
-
-$blue: rgb(0, 159, 230);
-$blue-l1: tint($blue,20%);
-$blue-l2: tint($blue,40%);
-$blue-l3: tint($blue,60%);
-$blue-l4: tint($blue,80%);
-$blue-l5: tint($blue,90%);
-$blue-d1: shade($blue,20%);
-$blue-d2: shade($blue,40%);
-$blue-d3: shade($blue,60%);
-$blue-d4: shade($blue,80%);
-$blue-s1: saturate($blue,15%);
-$blue-s2: saturate($blue,30%);
-$blue-s3: saturate($blue,45%);
-$blue-u1: desaturate($blue,15%);
-$blue-u2: desaturate($blue,30%);
-$blue-u3: desaturate($blue,45%);
-$blue-t0: rgba($blue, 0.125);
-$blue-t1: rgba($blue, 0.25);
-$blue-t2: rgba($blue, 0.50);
-$blue-t3: rgba($blue, 0.75);
-
-$pink: rgb(183, 37, 103); // #b72567;
-$pink-l1: tint($pink,20%);
-$pink-l2: tint($pink,40%);
-$pink-l3: tint($pink,60%);
-$pink-l4: tint($pink,80%);
-$pink-l5: tint($pink,90%);
-$pink-d1: shade($pink,20%);
-$pink-d2: shade($pink,40%);
-$pink-d3: shade($pink,60%);
-$pink-d4: shade($pink,80%);
-$pink-s1: saturate($pink,15%);
-$pink-s2: saturate($pink,30%);
-$pink-s3: saturate($pink,45%);
-$pink-u1: desaturate($pink,15%);
-$pink-u2: desaturate($pink,30%);
-$pink-u3: desaturate($pink,45%);
-
-$red: rgb(178, 6, 16); // #b20610;
-$red-l1: tint($red,20%);
-$red-l2: tint($red,40%);
-$red-l3: tint($red,60%);
-$red-l4: tint($red,80%);
-$red-l5: tint($red,90%);
-$red-d1: shade($red,20%);
-$red-d2: shade($red,40%);
-$red-d3: shade($red,60%);
-$red-d4: shade($red,80%);
-$red-s1: saturate($red,15%);
-$red-s2: saturate($red,30%);
-$red-s3: saturate($red,45%);
-$red-u1: desaturate($red,15%);
-$red-u2: desaturate($red,30%);
-$red-u3: desaturate($red,45%);
-
-$green: rgb(37, 184, 90); // #25b85a
-$green-l1: tint($green,20%);
-$green-l2: tint($green,40%);
-$green-l3: tint($green,60%);
-$green-l4: tint($green,80%);
-$green-l5: tint($green,90%);
-$green-d1: shade($green,20%);
-$green-d2: shade($green,40%);
-$green-d3: shade($green,60%);
-$green-d4: shade($green,80%);
-$green-s1: saturate($green,15%);
-$green-s2: saturate($green,30%);
-$green-s3: saturate($green,45%);
-$green-u1: desaturate($green,15%);
-$green-u2: desaturate($green,30%);
-$green-u3: desaturate($green,45%);
-
-$yellow: rgb(237, 189, 60);
-$yellow-l1: tint($yellow,20%);
-$yellow-l2: tint($yellow,40%);
-$yellow-l3: tint($yellow,60%);
-$yellow-l4: tint($yellow,80%);
-$yellow-l5: tint($yellow,90%);
-$yellow-d1: shade($yellow,20%);
-$yellow-d2: shade($yellow,40%);
-$yellow-d3: shade($yellow,60%);
-$yellow-d4: shade($yellow,80%);
-$yellow-s1: saturate($yellow,15%);
-$yellow-s2: saturate($yellow,30%);
-$yellow-s3: saturate($yellow,45%);
-$yellow-u1: desaturate($yellow,15%);
-$yellow-u2: desaturate($yellow,30%);
-$yellow-u3: desaturate($yellow,45%);
-
-$orange: rgb(237, 189, 60);
-$orange-l1: tint($orange,20%);
-$orange-l2: tint($orange,40%);
-$orange-l3: tint($orange,60%);
-$orange-l4: tint($orange,80%);
-$orange-l5: tint($orange,90%);
-$orange-d1: shade($orange,20%);
-$orange-d2: shade($orange,40%);
-$orange-d3: shade($orange,60%);
-$orange-d4: shade($orange,80%);
-$orange-s1: saturate($orange,15%);
-$orange-s2: saturate($orange,30%);
-$orange-s3: saturate($orange,45%);
-$orange-u1: desaturate($orange,15%);
-$orange-u2: desaturate($orange,30%);
-$orange-u3: desaturate($orange,45%);
-
-// +Colors - Shadows
-// ====================
-$shadow: rgba($black, 0.2);
-$shadow-l1: rgba($black, 0.1);
-$shadow-l2: rgba($black, 0.05);
-$shadow-d1: rgba($black, 0.4);
-$shadow-d2: rgba($black, 0.6);
-
-// +Colors - Application
-// ====================
-$color-draft: $gray-l3;
-$color-live: $blue;
-$color-ready: $green;
-$color-warning: $orange-l2;
-$color-error: $red-l2;
-$color-staff-only: $black;
-$color-gated: $black;
-$color-visibility-set: $black;
-
-$color-heading-base: $gray-d2;
-$color-copy-base: $gray-l1;
-$color-copy-emphasized: $gray-d2;
-
-// +Timing
-// ====================
-// used for animation/transition mixin syncing
-$tmg-s3: 3.0s;
-$tmg-s2: 2.0s;
-$tmg-s1: 1.0s;
-$tmg-avg: 0.75s;
-$tmg-f1: 0.50s;
-$tmg-f2: 0.25s;
-$tmg-f3: 0.125s;
-
-// +Archetype UI
-// ====================
-$ui-action-primary-color: $blue-u2;
-$ui-action-primary-color-focus: $blue-s1;
-
-$ui-link-color: $blue-u2;
-$ui-link-color-focus: $blue-s1;
-
-// +Specific UI
-// ====================
-$ui-notification-height: ($baseline*10);
-$ui-update-color: $blue-l4;
-
-// +Deprecated
-// ====================
-// do not use, future clean up will use updated styles
-$baseFontColor: $gray-d2;
-$lighter-base-font-color: rgb(100,100,100);
-$offBlack: #3c3c3c;
-$green: #108614;
-$lightGrey: #edf1f5;
-$mediumGrey: #b0b6c2;
-$darkGrey: #8891a1;
-$extraDarkGrey: #3d4043;
-$paleYellow: #fffcf1;
-$yellow: rgb(255, 254, 223);
-$green: rgb(37, 184, 90);
-$brightGreen: rgb(22, 202, 87);
-$disabledGreen: rgb(124, 206, 153);
-$darkGreen: rgb(52, 133, 76);
-
-// These colors are updated for testing purposes
-$lightBluishGrey: rgb(0, 250, 0);
-$lightBluishGrey2: rgb(0, 250, 0);
-$error-red: rgb(253, 87, 87);
-
-
-//carryover from LMS for xmodules
-$sidebar-color: rgb(246, 246, 246);
-
-// type
-$sans-serif: $f-sans-serif;
-$body-line-height: golden-ratio(.875em, 1);
-
-// carried over from LMS for xmodules
-$action-primary-active-bg: #1AA1DE; // $m-blue
-$very-light-text: $white;
diff --git a/common/test/test-theme/cms/templates/login.html b/common/test/test-theme/cms/templates/login.html
deleted file mode 100644
index 30757bfe11..0000000000
--- a/common/test/test-theme/cms/templates/login.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<%inherit file="base.html" />
-<%def name="online_help_token()"><% return "login" %>%def>
-<%!
-from django.core.urlresolvers import reverse
-from django.utils.translation import ugettext as _
-%>
-<%block name="title">${_("Sign In")}%block>
-<%block name="bodyclass">not-signedin view-signin%block>
-
-<%block name="content">
-
-%block>
-
-<%block name="requirejs">
- require(["js/factories/login"], function(LoginFactory) {
- LoginFactory("${reverse('homepage')}");
- });
-%block>
diff --git a/common/test/test-theme/lms/static/css/.gitignore b/common/test/test-theme/lms/static/css/.gitignore
deleted file mode 100644
index b3a5267117..0000000000
--- a/common/test/test-theme/lms/static/css/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-*.css
diff --git a/common/test/test-theme/lms/static/images/logo.png b/common/test/test-theme/lms/static/images/logo.png
deleted file mode 100644
index 5efc6b63a4..0000000000
Binary files a/common/test/test-theme/lms/static/images/logo.png and /dev/null differ
diff --git a/common/test/test-theme/lms/static/sass/partials/base/_variables.scss b/common/test/test-theme/lms/static/sass/partials/base/_variables.scss
deleted file mode 100755
index 43f66799a0..0000000000
--- a/common/test/test-theme/lms/static/sass/partials/base/_variables.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-@import 'lms/static/sass/partials/base/variables';
-
-$header-bg: rgb(0,250,0);
-$footer-bg: rgb(0,250,0);
-$container-bg: rgb(0,250,0);
diff --git a/common/test/test-theme/lms/templates/footer.html b/common/test/test-theme/lms/templates/footer.html
deleted file mode 100644
index c9e73c0117..0000000000
--- a/common/test/test-theme/lms/templates/footer.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/lms/djangoapps/branding/tests/test_views.py b/lms/djangoapps/branding/tests/test_views.py
index c6d4ee8340..a6cb5d7249 100644
--- a/lms/djangoapps/branding/tests/test_views.py
+++ b/lms/djangoapps/branding/tests/test_views.py
@@ -10,7 +10,7 @@ import mock
import ddt
from config_models.models import cache
from branding.models import BrandingApiConfig
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme_context
+from openedx.core.djangoapps.theming.test_util import with_edx_domain_context
@ddt.ddt
@@ -30,19 +30,19 @@ class TestFooter(TestCase):
@ddt.data(
# Open source version
- (None, "application/json", "application/json; charset=utf-8", "Open edX"),
- (None, "text/html", "text/html; charset=utf-8", "lms-footer.css"),
- (None, "text/html", "text/html; charset=utf-8", "Open edX"),
+ (False, "application/json", "application/json; charset=utf-8", "Open edX"),
+ (False, "text/html", "text/html; charset=utf-8", "lms-footer.css"),
+ (False, "text/html", "text/html; charset=utf-8", "Open edX"),
# EdX.org version
- ("edx.org", "application/json", "application/json; charset=utf-8", "edX Inc"),
- ("edx.org", "text/html", "text/html; charset=utf-8", "lms-footer-edx.css"),
- ("edx.org", "text/html", "text/html; charset=utf-8", "edX Inc"),
+ (True, "application/json", "application/json; charset=utf-8", "edX Inc"),
+ (True, "text/html", "text/html; charset=utf-8", "lms-footer-edx.css"),
+ (True, "text/html", "text/html; charset=utf-8", "edX Inc"),
)
@ddt.unpack
- def test_footer_content_types(self, theme, accepts, content_type, content):
+ def test_footer_content_types(self, is_edx_domain, accepts, content_type, content):
self._set_feature_flag(True)
- with with_comprehensive_theme_context(theme):
+ with with_edx_domain_context(is_edx_domain):
resp = self._get_footer(accepts=accepts)
self.assertEqual(resp.status_code, 200)
@@ -50,10 +50,10 @@ class TestFooter(TestCase):
self.assertIn(content, resp.content)
@mock.patch.dict(settings.FEATURES, {'ENABLE_FOOTER_MOBILE_APP_LINKS': True})
- @ddt.data("edx.org", None)
- def test_footer_json(self, theme):
+ @ddt.data(True, False)
+ def test_footer_json(self, is_edx_domain):
self._set_feature_flag(True)
- with with_comprehensive_theme_context(theme):
+ with with_edx_domain_context(is_edx_domain):
resp = self._get_footer()
self.assertEqual(resp.status_code, 200)
@@ -142,18 +142,18 @@ class TestFooter(TestCase):
@ddt.data(
# OpenEdX
- (None, "en", "lms-footer.css"),
- (None, "ar", "lms-footer-rtl.css"),
+ (False, "en", "lms-footer.css"),
+ (False, "ar", "lms-footer-rtl.css"),
# EdX.org
- ("edx.org", "en", "lms-footer-edx.css"),
- ("edx.org", "ar", "lms-footer-edx-rtl.css"),
+ (True, "en", "lms-footer-edx.css"),
+ (True, "ar", "lms-footer-edx-rtl.css"),
)
@ddt.unpack
- def test_language_rtl(self, theme, language, static_path):
+ def test_language_rtl(self, is_edx_domain, language, static_path):
self._set_feature_flag(True)
- with with_comprehensive_theme_context(theme):
+ with with_edx_domain_context(is_edx_domain):
resp = self._get_footer(accepts="text/html", params={'language': language})
self.assertEqual(resp.status_code, 200)
@@ -161,18 +161,18 @@ class TestFooter(TestCase):
@ddt.data(
# OpenEdX
- (None, True),
- (None, False),
+ (False, True),
+ (False, False),
# EdX.org
- ("edx.org", True),
- ("edx.org", False),
+ (True, True),
+ (True, False),
)
@ddt.unpack
- def test_show_openedx_logo(self, theme, show_logo):
+ def test_show_openedx_logo(self, is_edx_domain, show_logo):
self._set_feature_flag(True)
- with with_comprehensive_theme_context(theme):
+ with with_edx_domain_context(is_edx_domain):
params = {'show-openedx-logo': 1} if show_logo else {}
resp = self._get_footer(accepts="text/html", params=params)
@@ -185,17 +185,17 @@ class TestFooter(TestCase):
@ddt.data(
# OpenEdX
- (None, False),
- (None, True),
+ (False, False),
+ (False, True),
# EdX.org
- ("edx.org", False),
- ("edx.org", True),
+ (True, False),
+ (True, True),
)
@ddt.unpack
- def test_include_dependencies(self, theme, include_dependencies):
+ def test_include_dependencies(self, is_edx_domain, include_dependencies):
self._set_feature_flag(True)
- with with_comprehensive_theme_context(theme):
+ with with_edx_domain_context(is_edx_domain):
params = {'include-dependencies': 1} if include_dependencies else {}
resp = self._get_footer(accepts="text/html", params=params)
diff --git a/lms/djangoapps/commerce/tests/test_views.py b/lms/djangoapps/commerce/tests/test_views.py
index 1e5cd24f2a..59eadcb9e5 100644
--- a/lms/djangoapps/commerce/tests/test_views.py
+++ b/lms/djangoapps/commerce/tests/test_views.py
@@ -4,12 +4,13 @@ from uuid import uuid4
from nose.plugins.attrib import attr
import ddt
+from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase
import mock
from student.tests.factories import UserFactory
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme
+from openedx.core.djangoapps.theming.test_util import with_is_edx_domain
class UserMixin(object):
@@ -86,7 +87,7 @@ class ReceiptViewTests(UserMixin, TestCase):
self.assertRegexpMatches(response.content, user_message if is_user_message_expected else system_message)
self.assertNotRegexpMatches(response.content, user_message if not is_user_message_expected else system_message)
- @with_comprehensive_theme("edx.org")
+ @with_is_edx_domain(True)
def test_hide_nav_header(self):
self._login()
post_data = {'decision': 'ACCEPT', 'reason_code': '200', 'signed_field_names': 'dummy'}
diff --git a/lms/djangoapps/course_wiki/tests/test_comprehensive_theming.py b/lms/djangoapps/course_wiki/tests/test_comprehensive_theming.py
index 3042809e6d..f763510efb 100644
--- a/lms/djangoapps/course_wiki/tests/test_comprehensive_theming.py
+++ b/lms/djangoapps/course_wiki/tests/test_comprehensive_theming.py
@@ -1,6 +1,7 @@
"""
Tests for wiki middleware.
"""
+from django.conf import settings
from django.test.client import Client
from nose.plugins.attrib import attr
from wiki.models import URLPath
@@ -32,7 +33,7 @@ class TestComprehensiveTheming(ModuleStoreTestCase):
self.client = Client()
self.client.login(username='instructor', password='secret')
- @with_comprehensive_theme('red-theme')
+ @with_comprehensive_theme(settings.REPO_ROOT / 'themes/red-theme')
def test_themed_footer(self):
"""
Tests that theme footer is used rather than standard
diff --git a/lms/djangoapps/course_wiki/views.py b/lms/djangoapps/course_wiki/views.py
index 1913454e8c..06dfc63bca 100644
--- a/lms/djangoapps/course_wiki/views.py
+++ b/lms/djangoapps/course_wiki/views.py
@@ -6,6 +6,8 @@ import re
import cgi
from django.conf import settings
+from django.contrib.sites.models import Site
+from django.core.exceptions import ImproperlyConfigured
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
@@ -48,6 +50,18 @@ def course_wiki_redirect(request, course_id): # pylint: disable=unused-argument
if not valid_slug:
return redirect("wiki:get", path="")
+ # The wiki needs a Site object created. We make sure it exists here
+ try:
+ Site.objects.get_current()
+ except Site.DoesNotExist:
+ new_site = Site()
+ new_site.domain = settings.SITE_NAME
+ new_site.name = "edX"
+ new_site.save()
+ site_id = str(new_site.id)
+ if site_id != str(settings.SITE_ID):
+ raise ImproperlyConfigured("No site object was created and the SITE_ID doesn't match the newly created one. {} != {}".format(site_id, settings.SITE_ID))
+
try:
urlpath = URLPath.get_by_path(course_slug, select_related=True)
diff --git a/lms/djangoapps/courseware/tests/test_comprehensive_theming.py b/lms/djangoapps/courseware/tests/test_comprehensive_theming.py
index 26329f43b7..752eb81030 100644
--- a/lms/djangoapps/courseware/tests/test_comprehensive_theming.py
+++ b/lms/djangoapps/courseware/tests/test_comprehensive_theming.py
@@ -6,33 +6,21 @@ from django.test import TestCase
from path import path # pylint: disable=no-name-in-module
from django.contrib import staticfiles
-from paver.easy import call_task
-
from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme
-from openedx.core.lib.tempdir import mkdtemp_clean, mksym_link
+from openedx.core.lib.tempdir import mkdtemp_clean
class TestComprehensiveTheming(TestCase):
"""Test comprehensive theming."""
- @classmethod
- def setUpClass(cls):
- compile_sass('lms')
- super(TestComprehensiveTheming, cls).setUpClass()
-
def setUp(self):
super(TestComprehensiveTheming, self).setUp()
# Clear the internal staticfiles caches, to get test isolation.
staticfiles.finders.get_finder.cache_clear()
- @with_comprehensive_theme('red-theme')
+ @with_comprehensive_theme(settings.REPO_ROOT / 'themes/red-theme')
def test_red_footer(self):
- """
- Tests templates from theme are rendered if available.
- `red-theme` has header.html and footer.html so this test
- asserts presence of the content from header.html and footer.html
- """
resp = self.client.get('/')
self.assertEqual(resp.status_code, 200)
# This string comes from footer.html
@@ -46,16 +34,12 @@ class TestComprehensiveTheming(TestCase):
# of test.
# Make a temp directory as a theme.
- themes_dir = path(mkdtemp_clean())
- tmp_theme = "temp_theme"
- template_dir = themes_dir / tmp_theme / "lms/templates"
+ tmp_theme = path(mkdtemp_clean())
+ template_dir = tmp_theme / "lms/templates"
template_dir.makedirs()
with open(template_dir / "footer.html", "w") as footer:
footer.write("")
- dest_path = path(settings.COMPREHENSIVE_THEME_DIR) / tmp_theme
- mksym_link(themes_dir / tmp_theme, dest_path)
-
@with_comprehensive_theme(tmp_theme)
def do_the_test(self):
"""A function to do the work so we can use the decorator."""
@@ -66,18 +50,16 @@ class TestComprehensiveTheming(TestCase):
do_the_test(self)
def test_theme_adjusts_staticfiles_search_path(self):
- """
- Tests theme directories are added to staticfiles search path.
- """
+ # Test that a theme adds itself to the staticfiles search path.
before_finders = list(settings.STATICFILES_FINDERS)
before_dirs = list(settings.STATICFILES_DIRS)
- @with_comprehensive_theme('red-theme')
+ @with_comprehensive_theme(settings.REPO_ROOT / 'themes/red-theme')
def do_the_test(self):
"""A function to do the work so we can use the decorator."""
self.assertEqual(list(settings.STATICFILES_FINDERS), before_finders)
- self.assertIn(settings.REPO_ROOT / 'themes/red-theme/lms/static', settings.STATICFILES_DIRS)
- self.assertEqual(settings.STATICFILES_DIRS, before_dirs)
+ self.assertEqual(settings.STATICFILES_DIRS[0], settings.REPO_ROOT / 'themes/red-theme/lms/static')
+ self.assertEqual(settings.STATICFILES_DIRS[1:], before_dirs)
do_the_test(self)
@@ -85,9 +67,9 @@ class TestComprehensiveTheming(TestCase):
result = staticfiles.finders.find('images/logo.png')
self.assertEqual(result, settings.REPO_ROOT / 'lms/static/images/logo.png')
- @with_comprehensive_theme('red-theme')
+ @with_comprehensive_theme(settings.REPO_ROOT / 'themes/red-theme')
def test_overridden_logo_image(self):
- result = staticfiles.finders.find('red-theme/lms/static/images/logo.png')
+ result = staticfiles.finders.find('images/logo.png')
self.assertEqual(result, settings.REPO_ROOT / 'themes/red-theme/lms/static/images/logo.png')
def test_default_favicon(self):
@@ -97,54 +79,10 @@ class TestComprehensiveTheming(TestCase):
result = staticfiles.finders.find('images/favicon.ico')
self.assertEqual(result, settings.REPO_ROOT / 'lms/static/images/favicon.ico')
- @with_comprehensive_theme('red-theme')
- def test_css(self):
- """
- Test that static files finders are adjusted according to the applied comprehensive theme.
- """
- result = staticfiles.finders.find('red-theme/lms/static/css/lms-main.css')
- self.assertEqual(result, settings.REPO_ROOT / "themes/red-theme/lms/static/css/lms-main.css")
-
- lms_main_css = ""
- with open(result) as css_file:
- lms_main_css += css_file.read()
-
- self.assertIn("background:#fa0000", lms_main_css)
-
- def test_default_css(self):
- """
- Test default css is served if no theme is applied
- """
- result = staticfiles.finders.find('css/lms-main.css')
- self.assertEqual(result, settings.REPO_ROOT / "lms/static/css/lms-main.css")
-
- lms_main_css = ""
- with open(result) as css_file:
- lms_main_css += css_file.read()
-
- self.assertNotIn("background:#00fa00", lms_main_css)
-
- @with_comprehensive_theme('red-theme')
+ @with_comprehensive_theme(settings.REPO_ROOT / 'themes/red-theme')
def test_overridden_favicon(self):
"""
Test comprehensive theme override on favicon image.
"""
- result = staticfiles.finders.find('red-theme/lms/static/images/favicon.ico')
+ result = staticfiles.finders.find('images/favicon.ico')
self.assertEqual(result, settings.REPO_ROOT / 'themes/red-theme/lms/static/images/favicon.ico')
-
-
-def compile_sass(system):
- """
- Process xmodule assets and compile sass files.
-
- :param system - 'lms' or 'cms', specified the system to compile sass for.
- """
- # Compile system sass files
- call_task(
- 'pavelib.assets.update_assets',
- args=(
- system,
- "--themes_dir={themes_dir}".format(themes_dir=settings.COMPREHENSIVE_THEME_DIR),
- "--themes=red-theme",
- "--settings=test"),
- )
diff --git a/lms/djangoapps/courseware/tests/test_footer.py b/lms/djangoapps/courseware/tests/test_footer.py
index c1db2a77be..0a14ee1860 100644
--- a/lms/djangoapps/courseware/tests/test_footer.py
+++ b/lms/djangoapps/courseware/tests/test_footer.py
@@ -9,7 +9,7 @@ from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme
+from openedx.core.djangoapps.theming.test_util import with_is_edx_domain
@attr('shard_1')
@@ -37,7 +37,7 @@ class TestFooter(TestCase):
"youtube": "https://www.youtube.com/"
}
- @with_comprehensive_theme("edx.org")
+ @with_is_edx_domain(True)
def test_edx_footer(self):
"""
Verify that the homepage, when accessed at edx.org, has the edX footer
@@ -46,6 +46,7 @@ class TestFooter(TestCase):
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'footer-edx-v3')
+ @with_is_edx_domain(False)
def test_openedx_footer(self):
"""
Verify that the homepage, when accessed at something other than
@@ -55,7 +56,7 @@ class TestFooter(TestCase):
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'footer-openedx')
- @with_comprehensive_theme("edx.org")
+ @with_is_edx_domain(True)
@override_settings(
SOCIAL_MEDIA_FOOTER_NAMES=SOCIAL_MEDIA_NAMES,
SOCIAL_MEDIA_FOOTER_URLS=SOCIAL_MEDIA_URLS
diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py
index b9376b75c5..585233cb93 100644
--- a/lms/djangoapps/student_account/test/test_views.py
+++ b/lms/djangoapps/student_account/test/test_views.py
@@ -26,7 +26,7 @@ from student_account.views import account_settings_context
from third_party_auth.tests.testutil import simulate_running_pipeline, ThirdPartyAuthTestMixin
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme_context
+from openedx.core.djangoapps.theming.test_util import with_edx_domain_context
@ddt.ddt
@@ -241,13 +241,13 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
self.assertRedirects(response, reverse("dashboard"))
@ddt.data(
- (None, "signin_user"),
- (None, "register_user"),
- ("edx.org", "signin_user"),
- ("edx.org", "register_user"),
+ (False, "signin_user"),
+ (False, "register_user"),
+ (True, "signin_user"),
+ (True, "register_user"),
)
@ddt.unpack
- def test_login_and_registration_form_signin_preserves_params(self, theme, url_name):
+ def test_login_and_registration_form_signin_preserves_params(self, is_edx_domain, url_name):
params = [
('course_id', 'edX/DemoX/Demo_Course'),
('enrollment_action', 'enroll'),
@@ -255,7 +255,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
# The response should have a "Sign In" button with the URL
# that preserves the querystring params
- with with_comprehensive_theme_context(theme):
+ with with_edx_domain_context(is_edx_domain):
response = self.client.get(reverse(url_name), params)
expected_url = '/login?{}'.format(self._finish_auth_url_param(params + [('next', '/dashboard')]))
@@ -271,7 +271,7 @@ class StudentAccountLoginAndRegistrationTest(ThirdPartyAuthTestMixin, UrlResetMi
]
# Verify that this parameter is also preserved
- with with_comprehensive_theme_context(theme):
+ with with_edx_domain_context(is_edx_domain):
response = self.client.get(reverse(url_name), params)
expected_url = '/login?{}'.format(self._finish_auth_url_param(params))
diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py
index 2d6a41ccc3..2a876035ed 100644
--- a/lms/djangoapps/verify_student/tests/test_views.py
+++ b/lms/djangoapps/verify_student/tests/test_views.py
@@ -37,7 +37,7 @@ from common.test.utils import XssTestMixin
from commerce.tests import TEST_PAYMENT_DATA, TEST_API_URL, TEST_API_SIGNING_KEY
from embargo.test_utils import restrict_course
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme
+from openedx.core.djangoapps.theming.test_util import with_is_edx_domain
from shoppingcart.models import Order, CertificateItem
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from student.models import CourseEnrollment
@@ -283,7 +283,7 @@ class TestPayAndVerifyView(UrlResetMixin, ModuleStoreTestCase, XssTestMixin):
)
self._assert_redirects_to_dashboard(response)
- @with_comprehensive_theme("edx.org")
+ @with_is_edx_domain(True)
@ddt.data("verify_student_start_flow", "verify_student_begin_flow")
def test_pay_and_verify_hides_header_nav(self, payment_flow):
course = self._create_course("verified")
diff --git a/lms/envs/bok_choy.py b/lms/envs/bok_choy.py
index d4d8ff2be4..d461d4bbf3 100644
--- a/lms/envs/bok_choy.py
+++ b/lms/envs/bok_choy.py
@@ -61,9 +61,9 @@ STATIC_URL = "/static/"
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
)
-STATICFILES_DIRS = [
+STATICFILES_DIRS = (
(TEST_ROOT / "staticfiles" / "lms").abspath(),
-]
+)
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
MEDIA_ROOT = TEST_ROOT / "uploads"
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 6909527530..9948dedb90 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -397,7 +397,7 @@ COURSES_ROOT = ENV_ROOT / "data"
DATA_DIR = COURSES_ROOT
# comprehensive theming system
-COMPREHENSIVE_THEME_DIR = REPO_ROOT / "themes"
+COMPREHENSIVE_THEME_DIR = ""
# TODO: Remove the rest of the sys.path modification here and in cms/envs/common.py
sys.path.append(REPO_ROOT)
@@ -486,7 +486,6 @@ TEMPLATES = [
'loaders': [
# We have to use mako-aware template loaders to be able to include
# mako templates inside django templates (such as main_django.html).
- 'openedx.core.djangoapps.theming.template_loaders.ThemeFilesystemLoader',
'edxmako.makoloader.MakoFilesystemLoader',
'edxmako.makoloader.MakoAppDirectoriesLoader',
],
@@ -783,6 +782,7 @@ SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
CMS_BASE = 'localhost:8001'
# Site info
+SITE_ID = 1
SITE_NAME = "example.com"
HTTPS = 'on'
ROOT_URLCONF = 'lms.urls'
@@ -1172,7 +1172,6 @@ STATICFILES_STORAGE = 'openedx.core.storage.ProductionStorage'
# List of finder classes that know how to find static files in various locations.
# Note: the pipeline finder is included to be able to discover optimized files
STATICFILES_FINDERS = [
- 'openedx.core.djangoapps.theming.finders.ComprehensiveThemeFinder',
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'pipeline.finders.PipelineFinder',
@@ -2808,5 +2807,3 @@ AUDIT_CERT_CUTOFF_DATE = None
CREDENTIALS_SERVICE_USERNAME = 'credentials_service_user'
CREDENTIALS_GENERATION_ROUTING_KEY = HIGH_PRIORITY_QUEUE
-
-WIKI_REQUEST_CACHE_MIDDLEWARE_CLASS = "request_cache.middleware.RequestCache"
diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py
index 1657155966..e8ef6cd0a5 100644
--- a/lms/envs/devstack.py
+++ b/lms/envs/devstack.py
@@ -99,7 +99,6 @@ STATICFILES_STORAGE = 'openedx.core.storage.DevelopmentStorage'
# Revert to the default set of finders as we don't want the production pipeline
STATICFILES_FINDERS = [
- 'openedx.core.djangoapps.theming.finders.ComprehensiveThemeFinder',
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]
diff --git a/lms/envs/devstack_optimized.py b/lms/envs/devstack_optimized.py
index e0210ef6db..251ed4f343 100644
--- a/lms/envs/devstack_optimized.py
+++ b/lms/envs/devstack_optimized.py
@@ -38,6 +38,6 @@ STATIC_URL = "/static/"
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
)
-STATICFILES_DIRS = [
+STATICFILES_DIRS = (
(TEST_ROOT / "staticfiles" / "lms").abspath(),
-]
+)
diff --git a/lms/envs/test.py b/lms/envs/test.py
index d423b2ef21..cee2425db6 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -420,9 +420,6 @@ openid.oidutil.log = lambda message, level=0: None
PLATFORM_NAME = "edX"
SITE_NAME = "edx.org"
-# use default site for tests
-SITE_ID = 1
-
# set up some testing for microsites
FEATURES['USE_MICROSITES'] = True
MICROSITE_ROOT_DIR = COMMON_ROOT / 'test' / 'test_microsites'
@@ -492,8 +489,6 @@ MICROSITE_CONFIGURATION = {
MICROSITE_TEST_HOSTNAME = 'testmicrosite.testserver'
MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver'
-TEST_THEME = COMMON_ROOT / "test" / "test-theme"
-
# add extra template directory for test-only templates
MAKO_TEMPLATES['main'].extend([
COMMON_ROOT / 'test' / 'templates',
diff --git a/lms/startup.py b/lms/startup.py
index 2762e900ab..b6e63c3232 100644
--- a/lms/startup.py
+++ b/lms/startup.py
@@ -20,7 +20,7 @@ from monkey_patch import (
import xmodule.x_module
import lms_xblock.runtime
-from openedx.core.djangoapps.theming.core import enable_comprehensive_theming
+from openedx.core.djangoapps.theming.core import enable_comprehensive_theme
from microsite_configuration import microsite
log = logging.getLogger(__name__)
@@ -40,7 +40,7 @@ def run():
# Comprehensive theming needs to be set up before django startup,
# because modifying django template paths after startup has no effect.
if settings.COMPREHENSIVE_THEME_DIR:
- enable_comprehensive_theming(settings.COMPREHENSIVE_THEME_DIR)
+ enable_comprehensive_theme(settings.COMPREHENSIVE_THEME_DIR)
# We currently use 2 template rendering engines, mako and django_templates,
# and one of them (django templates), requires the directories be added
diff --git a/lms/static/sass/partials/base/_variables.scss b/lms/static/sass/base/_variables.scss
similarity index 100%
rename from lms/static/sass/partials/base/_variables.scss
rename to lms/static/sass/base/_variables.scss
diff --git a/lms/templates/main_django.html b/lms/templates/main_django.html
index fe9d8a65a8..59c5e22078 100644
--- a/lms/templates/main_django.html
+++ b/lms/templates/main_django.html
@@ -1,5 +1,5 @@
-{% load sekizai_tags i18n microsite theme_pipeline optional_include %}
+{% load sekizai_tags i18n microsite pipeline optional_include %}
{% load url from future %}
diff --git a/lms/templates/wiki/base.html b/lms/templates/wiki/base.html
index 99b3cebbbc..52f528168f 100644
--- a/lms/templates/wiki/base.html
+++ b/lms/templates/wiki/base.html
@@ -1,5 +1,5 @@
{% extends "main_django.html" %}
-{% load theme_pipeline %}{% load sekizai_tags i18n microsite %}{% load url from future %}{% load staticfiles %}
+{% load pipeline %}{% load sekizai_tags i18n microsite %}{% load url from future %}{% load staticfiles %}
{% block title %}{% block pagetitle %}{% endblock %} | {% trans "Wiki" %} | {% platform_name %}{% endblock %}
diff --git a/lms/templates/wiki/preview_inline.html b/lms/templates/wiki/preview_inline.html
index a2e44b3526..75f57e9603 100644
--- a/lms/templates/wiki/preview_inline.html
+++ b/lms/templates/wiki/preview_inline.html
@@ -1,5 +1,5 @@
-{% load wiki_tags i18n %}{% load theme_pipeline %}
+{% load wiki_tags i18n %}{% load pipeline %}
{% stylesheet 'course' %}
diff --git a/openedx/core/djangoapps/theming/admin.py b/openedx/core/djangoapps/theming/admin.py
deleted file mode 100644
index 690016f8c8..0000000000
--- a/openedx/core/djangoapps/theming/admin.py
+++ /dev/null
@@ -1,22 +0,0 @@
-"""
-Django admin page for theming models
-"""
-from django.contrib import admin
-
-from .models import (
- SiteTheme,
-)
-
-
-class SiteThemeAdmin(admin.ModelAdmin):
- """ Admin interface for the SiteTheme object. """
- list_display = ('site', 'theme_dir_name')
- search_fields = ('site__domain', 'theme_dir_name')
-
- class Meta(object):
- """
- Meta class for SiteTheme admin model
- """
- model = SiteTheme
-
-admin.site.register(SiteTheme, SiteThemeAdmin)
diff --git a/openedx/core/djangoapps/theming/core.py b/openedx/core/djangoapps/theming/core.py
index 86bc27ed9f..9203cc5c1f 100644
--- a/openedx/core/djangoapps/theming/core.py
+++ b/openedx/core/djangoapps/theming/core.py
@@ -1,32 +1,62 @@
"""
Core logic for Comprehensive Theming.
"""
-import os.path
-from path import Path as path
+from path import Path
+
from django.conf import settings
-from .helpers import (
- get_project_root_name,
-)
+
+def comprehensive_theme_changes(theme_dir):
+ """
+ Calculate the set of changes needed to enable a comprehensive theme.
+
+ Arguments:
+ theme_dir (path.path): the full path to the theming directory to use.
+
+ Returns:
+ A dict indicating the changes to make:
+
+ * 'settings': a dictionary of settings names and their new values.
+
+ * 'template_paths': a list of directories to prepend to template
+ lookup path.
+
+ """
+
+ changes = {
+ 'settings': {},
+ 'template_paths': [],
+ }
+ root = Path(settings.PROJECT_ROOT)
+ if root.name == "":
+ root = root.parent
+
+ component_dir = theme_dir / root.name
+
+ templates_dir = component_dir / "templates"
+ if templates_dir.isdir():
+ changes['template_paths'].append(templates_dir)
+
+ staticfiles_dir = component_dir / "static"
+ if staticfiles_dir.isdir():
+ changes['settings']['STATICFILES_DIRS'] = [staticfiles_dir] + settings.STATICFILES_DIRS
+
+ locale_dir = component_dir / "conf" / "locale"
+ if locale_dir.isdir():
+ changes['settings']['LOCALE_PATHS'] = [locale_dir] + settings.LOCALE_PATHS
+
+ return changes
-def enable_comprehensive_theming(themes_dir):
+def enable_comprehensive_theme(theme_dir):
"""
Add directories to relevant paths for comprehensive theming.
- :param themes_dir: path to base theme directory
"""
- if isinstance(themes_dir, basestring):
- themes_dir = path(themes_dir)
+ changes = comprehensive_theme_changes(theme_dir)
- if themes_dir.isdir():
- settings.DEFAULT_TEMPLATE_ENGINE['DIRS'].insert(0, themes_dir)
- settings.MAKO_TEMPLATES['main'].insert(0, themes_dir)
-
- for theme_dir in os.listdir(themes_dir):
- staticfiles_dir = os.path.join(themes_dir, theme_dir, get_project_root_name(), "static")
- if staticfiles_dir.isdir():
- settings.STATICFILES_DIRS = settings.STATICFILES_DIRS + [staticfiles_dir]
-
- locale_dir = os.path.join(themes_dir, theme_dir, get_project_root_name(), "conf", "locale")
- if locale_dir.isdir():
- settings.LOCALE_PATHS = (locale_dir, ) + settings.LOCALE_PATHS
+ # Use the changes
+ for name, value in changes['settings'].iteritems():
+ setattr(settings, name, value)
+ for template_dir in changes['template_paths']:
+ settings.DEFAULT_TEMPLATE_ENGINE['DIRS'].insert(0, template_dir)
+ settings.MAKO_TEMPLATES['main'].insert(0, template_dir)
diff --git a/openedx/core/djangoapps/theming/finders.py b/openedx/core/djangoapps/theming/finders.py
index 6435e900c6..cbf4366f5a 100644
--- a/openedx/core/djangoapps/theming/finders.py
+++ b/openedx/core/djangoapps/theming/finders.py
@@ -22,10 +22,7 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.contrib.staticfiles import utils
from django.contrib.staticfiles.finders import BaseFinder
-from openedx.core.djangoapps.theming.helpers import (
- get_base_theme_dir
-)
-from openedx.core.djangoapps.theming.storage import ComprehensiveThemingStorage
+from openedx.core.djangoapps.theming.storage import CachedComprehensiveThemingStorage
class ComprehensiveThemeFinder(BaseFinder):
@@ -36,21 +33,23 @@ class ComprehensiveThemeFinder(BaseFinder):
this finder will never find any files.
"""
def __init__(self, *args, **kwargs):
- """
- Initialize finder with comprehensive theming storage if we have
- a valid COMPREHENSIVE_THEME_DIR setting.
- """
super(ComprehensiveThemeFinder, self).__init__(*args, **kwargs)
- themes_dir = get_base_theme_dir()
- if not themes_dir:
+ theme_dir = getattr(settings, "COMPREHENSIVE_THEME_DIR", "")
+ if not theme_dir:
self.storage = None
return
- if not isinstance(themes_dir, basestring):
+ if not isinstance(theme_dir, basestring):
raise ImproperlyConfigured("Your COMPREHENSIVE_THEME_DIR setting must be a string")
- self.storage = ComprehensiveThemingStorage(location=themes_dir)
+ root = Path(settings.PROJECT_ROOT)
+ if root.name == "":
+ root = root.parent
+
+ component_dir = Path(theme_dir) / root.name
+ static_dir = component_dir / "static"
+ self.storage = CachedComprehensiveThemingStorage(location=static_dir)
def find(self, path, all=False): # pylint: disable=redefined-builtin
"""
@@ -59,6 +58,10 @@ class ComprehensiveThemeFinder(BaseFinder):
if not self.storage:
return []
+ if path.startswith(self.storage.prefix):
+ # strip the prefix
+ path = path[len(self.storage.prefix):]
+
if self.storage.exists(path):
match = self.storage.path(path)
if all:
diff --git a/openedx/core/djangoapps/theming/helpers.py b/openedx/core/djangoapps/theming/helpers.py
index 090f186e39..28ce710ebe 100644
--- a/openedx/core/djangoapps/theming/helpers.py
+++ b/openedx/core/djangoapps/theming/helpers.py
@@ -1,18 +1,9 @@
"""
Helpers for accessing comprehensive theming related variables.
"""
-import re
-import os.path
-from path import Path
-
-from django.conf import settings
-from django.contrib.sites.shortcuts import get_current_site
-from django.contrib.sites.models import Site
-from django.core.cache import cache
-from django.contrib.staticfiles.storage import staticfiles_storage
-
from microsite_configuration import microsite
from microsite_configuration import page_title_breadcrumbs
+from django.conf import settings
def get_page_title_breadcrumbs(*args):
@@ -33,11 +24,7 @@ def get_template_path(relative_path, **kwargs):
"""
This is a proxy function to hide microsite_configuration behind comprehensive theming.
"""
- template_path = get_template_path_with_theme(relative_path)
- if template_path == relative_path: # we don't have a theme now look into microsites
- template_path = microsite.get_template_path(relative_path, **kwargs)
-
- return template_path
+ return microsite.get_template_path(relative_path, **kwargs)
def is_request_in_themed_site():
@@ -47,14 +34,6 @@ def is_request_in_themed_site():
return microsite.is_request_in_microsite()
-def get_template(uri):
- """
- This is a proxy function to hide microsite_configuration behind comprehensive theming.
- :param uri: uri of the template
- """
- return microsite.get_template(uri)
-
-
def get_themed_template_path(relative_path, default_path, **kwargs):
"""
This is a proxy function to hide microsite_configuration behind comprehensive theming.
@@ -73,264 +52,3 @@ def get_themed_template_path(relative_path, default_path, **kwargs):
if is_stanford_theming_enabled and not is_microsite:
return relative_path
return microsite.get_template_path(default_path, **kwargs)
-
-
-def get_template_path_with_theme(relative_path):
- """
- Returns template path in current site's theme if it finds one there otherwise returns same path.
-
- Example:
- >> get_template_path_with_theme('header')
- '/red-theme/lms/templates/header.html'
-
- Parameters:
- relative_path (str): template's path relative to the templates directory e.g. 'footer.html'
-
- Returns:
- (str): template path in current site's theme
- """
- site_theme_dir = get_current_site_theme_dir()
- if not site_theme_dir:
- return relative_path
-
- base_theme_dir = get_base_theme_dir()
- root_name = get_project_root_name()
- template_path = "/".join([
- base_theme_dir,
- site_theme_dir,
- root_name,
- "templates"
- ])
-
- # strip `/` if present at the start of relative_path
- template_name = re.sub(r'^/+', '', relative_path)
- search_path = os.path.join(template_path, template_name)
- if os.path.isfile(search_path):
- path = '/{site_theme_dir}/{root_name}/templates/{template_name}'.format(
- site_theme_dir=site_theme_dir,
- root_name=root_name,
- template_name=template_name,
- )
- return path
- else:
- return relative_path
-
-
-def get_current_theme_template_dirs():
- """
- Returns template directories for the current theme.
-
- Example:
- >> get_current_theme_template_dirs('header.html')
- ['/edx/app/edxapp/edx-platform/themes/red-theme/lms/templates/', ]
-
- Returns:
- (list): list of directories containing theme templates.
- """
- site_theme_dir = get_current_site_theme_dir()
- if not site_theme_dir:
- return None
-
- base_theme_dir = get_base_theme_dir()
- root_name = get_project_root_name()
- template_path = "/".join([
- base_theme_dir,
- site_theme_dir,
- root_name,
- "templates"
- ])
-
- return [template_path]
-
-
-def strip_site_theme_templates_path(uri):
- """
- Remove site template theme path from the uri.
-
- Example:
- >> strip_site_theme_templates_path('/red-theme/lms/templates/header.html')
- 'header.html'
-
- Arguments:
- uri (str): template path from which to remove site theme path. e.g. '/red-theme/lms/templates/header.html'
-
- Returns:
- (str): template path with site theme path removed.
- """
- site_theme_dir = get_current_site_theme_dir()
- if not site_theme_dir:
- return uri
-
- root_name = get_project_root_name()
- templates_path = "/".join([
- site_theme_dir,
- root_name,
- "templates"
- ])
-
- uri = re.sub(r'^/*' + templates_path + '/*', '', uri)
- return uri
-
-
-def get_current_site_theme_dir():
- """
- Return theme directory for the current site.
-
- Example:
- >> get_current_site_theme_dir()
- 'red-theme'
-
- Returns:
- (str): theme directory for current site
- """
- from edxmako.middleware import REQUEST_CONTEXT
- request = getattr(REQUEST_CONTEXT, 'request', None)
- if not request:
- return None
-
- # if hostname is not valid
- if not all((isinstance(request.get_host(), basestring), is_valid_hostname(request.get_host()))):
- return None
-
- try:
- site = get_current_site(request)
- except Site.DoesNotExist:
- return None
- site_theme_dir = cache.get(get_site_theme_cache_key(site))
-
- # if site theme dir is not in cache and comprehensive theming is enabled then pull it from db.
- if not site_theme_dir and is_comprehensive_theming_enabled():
- site_theme = site.themes.first() # pylint: disable=no-member
- if site_theme:
- site_theme_dir = site_theme.theme_dir_name
- cache_site_theme_dir(site, site_theme_dir)
- return site_theme_dir
-
-
-def get_project_root_name():
- """
- Return root name for the current project
-
- Example:
- >> get_project_root_name()
- 'lms'
- # from studio
- >> get_project_root_name()
- 'cms'
-
- Returns:
- (str): component name of platform e.g lms, cms
- """
- root = Path(settings.PROJECT_ROOT)
- if root.name == "":
- root = root.parent
- return root.name
-
-
-def get_base_theme_dir():
- """
- Return base directory that contains all the themes.
-
- Example:
- >> get_base_theme_dir()
- '/edx/app/edxapp/edx-platform/themes'
-
- Returns:
- (str): Base theme directory
- """
- return settings.COMPREHENSIVE_THEME_DIR
-
-
-def is_comprehensive_theming_enabled():
- """
- Returns boolean indicating whether comprehensive theming functionality is enabled or disabled.
- Example:
- >> is_comprehensive_theming_enabled()
- True
-
- Returns:
- (bool): True if comprehensive theming is enabled else False
- """
- return True if settings.COMPREHENSIVE_THEME_DIR else False
-
-
-def get_site_theme_cache_key(site):
- """
- Return cache key for the given site.
-
- Example:
- >> site = Site(domain='red-theme.org', name='Red Theme')
- >> get_site_theme_cache_key(site)
- 'theming.site.red-theme.org'
-
- Parameters:
- site (django.contrib.sites.models.Site): site where key needs to generated
- Returns:
- (str): a key to be used as cache key
- """
- cache_key = "theming.site.{domain}".format(
- domain=site.domain
- )
- return cache_key
-
-
-def is_valid_hostname(hostname):
- """
- Return boolean indicating if given hostname is valid or not
-
- Example:
- >> is_valid_hostname('red-theme.org')
- True
-
- Parameters:
- hostname (str): hostname that needs to be tested.
- Returns:
- (bool): True if given hostname is valid else False
-
- """
- if len(hostname) > 255 or "." not in hostname:
- return False
- if hostname[-1] == ".":
- hostname = hostname[:-1] # strip exactly one dot from the right, if present
- if ":" in hostname:
- hostname = hostname.split(":")[0] # strip port number if present
-
- allowed = re.compile(r"(?!-)[A-Z\d-]{1,63}(?> site = Site(domain='red-theme.org', name='Red Theme')
- >> cache_site_theme_dir(site, 'red-theme')
-
- Parameters:
- site (django.contrib.sites.models.Site): site for to cache
- theme_dir (str): theme directory for the given site
- """
- cache.set(get_site_theme_cache_key(site), theme_dir, settings.FOOTER_CACHE_TIMEOUT)
-
-
-def get_static_file_url(asset):
- """
- Returns url of the themed asset if asset is not themed than returns the default asset url.
-
- Example:
- >> get_static_file_url('css/lms-main.css', 'red-theme')
- '/static/red-theme/css/lms-main.css'
-
- Parameters:
- asset (str): asset's path relative to the static files directory
-
- Returns:
- (str): static asset's url
- """
- theme = get_current_site_theme_dir()
- try:
- return staticfiles_storage.url(asset, theme)
- except (ValueError, TypeError):
- # in case of an error return url without theme applied
- return staticfiles_storage.url(asset)
diff --git a/openedx/core/djangoapps/theming/migrations/0001_initial.py b/openedx/core/djangoapps/theming/migrations/0001_initial.py
deleted file mode 100644
index ebf80f9d3e..0000000000
--- a/openedx/core/djangoapps/theming/migrations/0001_initial.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('sites', '0001_initial'),
- ]
-
- operations = [
- migrations.CreateModel(
- name='SiteTheme',
- fields=[
- ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
- ('theme_dir_name', models.CharField(max_length=255)),
- ('site', models.ForeignKey(related_name='themes', to='sites.Site')),
- ],
- ),
- ]
diff --git a/openedx/core/djangoapps/theming/migrations/__init__.py b/openedx/core/djangoapps/theming/migrations/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/openedx/core/djangoapps/theming/models.py b/openedx/core/djangoapps/theming/models.py
deleted file mode 100644
index d3e70189de..0000000000
--- a/openedx/core/djangoapps/theming/models.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
-Comprehensive Theme related models.
-"""
-from django.db import models
-from django.contrib.sites.models import Site
-
-
-class SiteTheme(models.Model):
- """
- This is where the information about the site's theme gets stored to the db.
-
- `site` field is foreignkey to django Site model
- `theme_dir_name` contains directory name having Site's theme
- """
- site = models.ForeignKey(Site, related_name='themes')
- theme_dir_name = models.CharField(max_length=255)
-
- def __unicode__(self):
- return self.theme_dir_name
diff --git a/openedx/core/djangoapps/theming/storage.py b/openedx/core/djangoapps/theming/storage.py
index a546b72086..3fb5311b5a 100644
--- a/openedx/core/djangoapps/theming/storage.py
+++ b/openedx/core/djangoapps/theming/storage.py
@@ -2,83 +2,87 @@
Comprehensive Theming support for Django's collectstatic functionality.
See https://docs.djangoproject.com/en/1.8/ref/contrib/staticfiles/
"""
-
+from path import Path
import os.path
-import re
-
-from django.core.exceptions import ImproperlyConfigured
-from django.contrib.staticfiles.storage import StaticFilesStorage
-from django.utils._os import safe_join
from django.conf import settings
-
-from openedx.core.djangoapps.theming.helpers import (
- get_base_theme_dir,
- get_project_root_name,
- get_current_site_theme_dir,
-)
+from django.core.exceptions import ImproperlyConfigured
+from django.contrib.staticfiles.storage import StaticFilesStorage, CachedFilesMixin
+from django.utils._os import safe_join
-class ComprehensiveThemingStorage(StaticFilesStorage):
+class ComprehensiveThemingAwareMixin(object):
"""
Mixin for Django storage system to make it aware of the currently-active
comprehensive theme, so that it can generate theme-scoped URLs for themed
static assets.
"""
def __init__(self, *args, **kwargs):
- super(ComprehensiveThemingStorage, self).__init__(*args, **kwargs)
- themes_dir = get_base_theme_dir()
- if not themes_dir:
- self.themes_location = None
+ super(ComprehensiveThemingAwareMixin, self).__init__(*args, **kwargs)
+ theme_dir = getattr(settings, "COMPREHENSIVE_THEME_DIR", "")
+ if not theme_dir:
+ self.theme_location = None
return
- if not isinstance(themes_dir, basestring):
+ if not isinstance(theme_dir, basestring):
raise ImproperlyConfigured("Your COMPREHENSIVE_THEME_DIR setting must be a string")
- self.themes_location = themes_dir
+ root = Path(settings.PROJECT_ROOT)
+ if root.name == "":
+ root = root.parent
- def themed(self, name, theme_dir):
+ component_dir = Path(theme_dir) / root.name
+ self.theme_location = component_dir / "static"
+
+ @property
+ def prefix(self):
+ """
+ This is used by the ComprehensiveThemeFinder in the collection step.
+ """
+ theme_dir = getattr(settings, "COMPREHENSIVE_THEME_DIR", "")
+ if not theme_dir:
+ return None
+ theme_name = os.path.basename(os.path.normpath(theme_dir))
+ return "themes/{name}/".format(name=theme_name)
+
+ def themed(self, name):
"""
Given a name, return a boolean indicating whether that name exists
as a themed asset in the comprehensive theme.
"""
- # Nothing can be themed if we don't have a theme location or required params.
- if not all((self.themes_location, theme_dir, name)):
+ # Nothing can be themed if we don't have a theme location.
+ if not self.theme_location:
return False
- themed_path = "/".join([
- self.themes_location,
- theme_dir,
- get_project_root_name(),
- "static/"
- ])
- name = name[1:] if name.startswith("/") else name
- path = safe_join(themed_path, name)
+ path = safe_join(self.theme_location, name)
return os.path.exists(path)
def path(self, name):
"""
Get the path to the real asset on disk
"""
- try:
- theme_dir, asset_path = name.split("/", 1)
- if self.themed(asset_path, theme_dir):
- name = asset_path
- base = self.themes_location + "/" + theme_dir + "/" + get_project_root_name() + "/static/"
- else:
- base = self.location
- except ValueError:
- # in case we don't '/' in name
+ if self.themed(name):
+ base = self.theme_location
+ else:
base = self.location
- if base == settings.STATIC_ROOT:
- name = re.sub(r"/?(?P[^/]+)/(?Plms|cms)/static/", r"\g/", name)
path = safe_join(base, name)
return os.path.normpath(path)
- def url(self, name):
+ def url(self, name, *args, **kwargs):
"""
Add the theme prefix to the asset URL
"""
- theme_dir = get_current_site_theme_dir()
- if self.themed(name, theme_dir):
- name = theme_dir + "/" + name
- return super(ComprehensiveThemingStorage, self).url(name)
+ if self.themed(name):
+ name = self.prefix + name
+ return super(ComprehensiveThemingAwareMixin, self).url(name, *args, **kwargs)
+
+
+class CachedComprehensiveThemingStorage(
+ ComprehensiveThemingAwareMixin,
+ CachedFilesMixin,
+ StaticFilesStorage
+):
+ """
+ Used by the ComprehensiveThemeFinder class. Mixes in support for cached
+ files and comprehensive theming in static files.
+ """
+ pass
diff --git a/openedx/core/djangoapps/theming/template_loaders.py b/openedx/core/djangoapps/theming/template_loaders.py
deleted file mode 100644
index 62b8fbf349..0000000000
--- a/openedx/core/djangoapps/theming/template_loaders.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
-Theming aware template loaders.
-"""
-
-import logging
-
-from django.template.loaders.filesystem import Loader as FilesystemLoader
-
-from edxmako.makoloader import MakoLoader
-from openedx.core.djangoapps.theming.helpers import get_template_path_with_theme
-
-log = logging.getLogger(__name__)
-
-
-class ThemeTemplateLoader(MakoLoader):
- """
- This is a Django loader object which will load the template based on current request and its corresponding theme.
- """
- def __call__(self, template_name, template_dirs=None):
- template_name = get_template_path_with_theme(template_name).lstrip("/")
- return self.load_template(template_name, template_dirs)
-
-
-class ThemeFilesystemLoader(ThemeTemplateLoader):
- """
- Filesystem Template loaders to pickup templates from theme directory based on the current site.
- """
- is_usable = True
- _accepts_engine_in_init = True
-
- def __init__(self, *args):
- ThemeTemplateLoader.__init__(self, FilesystemLoader(*args))
diff --git a/openedx/core/djangoapps/theming/templatetags/theme_pipeline.py b/openedx/core/djangoapps/theming/templatetags/theme_pipeline.py
deleted file mode 100644
index 7beb99ca55..0000000000
--- a/openedx/core/djangoapps/theming/templatetags/theme_pipeline.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-Theme aware pipeline template tags.
-"""
-
-from django import template
-from django.template.loader import render_to_string
-from django.utils.safestring import mark_safe
-
-from pipeline.templatetags.pipeline import StylesheetNode, JavascriptNode
-from pipeline.utils import guess_type
-
-from openedx.core.djangoapps.theming.helpers import get_static_file_url
-
-register = template.Library() # pylint: disable=invalid-name
-
-
-class ThemeStylesheetNode(StylesheetNode):
- """
- Overrides StyleSheetNode from django pipeline so that stylesheets are served based on the applied theme.
- """
- def render_css(self, package, path):
- """
- Override render_css from django-pipline so that stylesheets urls are based on the applied theme
- """
- template_name = package.template_name or "pipeline/css.html"
- context = package.extra_context
- context.update({
- 'type': guess_type(path, 'text/css'),
- 'url': mark_safe(get_static_file_url(path))
- })
- return render_to_string(template_name, context)
-
-
-class ThemeJavascriptNode(JavascriptNode):
- """
- Overrides JavascriptNode from django pipeline so that js files are served based on the applied theme.
- """
- def render_js(self, package, path):
- """
- Override render_js from django-pipline so that js file urls are based on the applied theme
- """
- template_name = package.template_name or "pipeline/js.html"
- context = package.extra_context
- context.update({
- 'type': guess_type(path, 'text/javascript'),
- 'url': mark_safe(get_static_file_url(path))
- })
- return render_to_string(template_name, context)
-
-
-@register.tag
-def stylesheet(parser, token): # pylint: disable=unused-argument
- """
- Template tag to serve stylesheets from django-pipeline. This definition uses the theming aware ThemeStyleSheetNode.
- """
- try:
- _, name = token.split_contents()
- except ValueError:
- raise template.TemplateSyntaxError(
- '%r requires exactly one argument: the name of a group in the PIPELINE_CSS setting' %
- token.split_contents()[0]
- )
- return ThemeStylesheetNode(name)
-
-
-@register.tag
-def javascript(parser, token): # pylint: disable=unused-argument
- """
- Template tag to serve javascript from django-pipeline. This definition uses the theming aware ThemeJavascriptNode.
- """
- try:
- _, name = token.split_contents()
- except ValueError:
- raise template.TemplateSyntaxError(
- '%r requires exactly one argument: the name of a group in the PIPELINE_JS setting' %
- token.split_contents()[0]
- )
- return ThemeJavascriptNode(name)
diff --git a/openedx/core/djangoapps/theming/test_util.py b/openedx/core/djangoapps/theming/test_util.py
index f75957ba1f..fbd871c213 100644
--- a/openedx/core/djangoapps/theming/test_util.py
+++ b/openedx/core/djangoapps/theming/test_util.py
@@ -6,57 +6,87 @@ from functools import wraps
import os
import os.path
import contextlib
-import re
from mock import patch
from django.conf import settings
-from django.contrib.sites.models import Site
+from django.template import Engine
+from django.test.utils import override_settings
import edxmako
-from .models import SiteTheme
+
+from .core import comprehensive_theme_changes
+
+EDX_THEME_DIR = settings.REPO_ROOT / "themes" / "edx.org"
-def with_comprehensive_theme(theme_dir_name):
+def with_comprehensive_theme(theme_dir):
"""
- A decorator to run a test with a comprehensive theming enabled.
+ A decorator to run a test with a particular comprehensive theme.
+
Arguments:
- theme_dir_name (str): directory name of the site for which we want comprehensive theming enabled.
+ theme_dir (str): the full path to the theme directory to use.
+ This will likely use `settings.REPO_ROOT` to get the full path.
+
"""
- # This decorator creates Site and SiteTheme models for given domain
+ # This decorator gets the settings changes needed for a theme, and applies
+ # them using the override_settings and edxmako.paths.add_lookup context
+ # managers.
+
+ changes = comprehensive_theme_changes(theme_dir)
+
def _decorator(func): # pylint: disable=missing-docstring
@wraps(func)
def _decorated(*args, **kwargs): # pylint: disable=missing-docstring
- # make a domain name out of directory name
- domain = "{theme_dir_name}.org".format(theme_dir_name=re.sub(r"\.org$", "", theme_dir_name))
- site, __ = Site.objects.get_or_create(domain=domain, name=domain)
- SiteTheme.objects.get_or_create(site=site, theme_dir_name=theme_dir_name)
- edxmako.paths.add_lookup('main', settings.COMPREHENSIVE_THEME_DIR, prepend=True)
- with patch('openedx.core.djangoapps.theming.helpers.get_current_site_theme_dir',
- return_value=theme_dir_name):
- with patch('openedx.core.djangoapps.theming.helpers.get_current_site', return_value=site):
- return func(*args, **kwargs)
+ with override_settings(COMPREHENSIVE_THEME_DIR=theme_dir, **changes['settings']):
+ default_engine = Engine.get_default()
+ dirs = default_engine.dirs[:]
+ with edxmako.save_lookups():
+ for template_dir in changes['template_paths']:
+ edxmako.paths.add_lookup('main', template_dir, prepend=True)
+ dirs.insert(0, template_dir)
+ with patch.object(default_engine, 'dirs', dirs):
+ return func(*args, **kwargs)
return _decorated
return _decorator
-@contextlib.contextmanager
-def with_comprehensive_theme_context(theme=None):
+def with_is_edx_domain(is_edx_domain):
"""
- A function to run a test as if request was made to the given theme.
+ A decorator to run a test as if request originated from edX domain or not.
Arguments:
- theme (str): name if the theme or None if no theme is applied
+ is_edx_domain (bool): are we an edX domain or not?
"""
- if theme:
- domain = '{theme}.org'.format(theme=re.sub(r"\.org$", "", theme))
- site, __ = Site.objects.get_or_create(domain=domain, name=theme)
- SiteTheme.objects.get_or_create(site=site, theme_dir_name=theme)
- edxmako.paths.add_lookup('main', settings.COMPREHENSIVE_THEME_DIR, prepend=True)
- with patch('openedx.core.djangoapps.theming.helpers.get_current_site_theme_dir',
- return_value=theme):
- with patch('openedx.core.djangoapps.theming.helpers.get_current_site', return_value=site):
+ # This is weird, it's a decorator that conditionally applies other
+ # decorators, which is confusing.
+ def _decorator(func): # pylint: disable=missing-docstring
+ if is_edx_domain:
+ # This applies @with_comprehensive_theme to the func.
+ func = with_comprehensive_theme(EDX_THEME_DIR)(func)
+
+ return func
+
+ return _decorator
+
+
+@contextlib.contextmanager
+def with_edx_domain_context(is_edx_domain):
+ """
+ A function to run a test as if request originated from edX domain or not.
+
+ Arguments:
+ is_edx_domain (bool): are we an edX domain or not?
+
+ """
+ if is_edx_domain:
+ changes = comprehensive_theme_changes(EDX_THEME_DIR)
+ with override_settings(COMPREHENSIVE_THEME_DIR=EDX_THEME_DIR, **changes['settings']):
+ with edxmako.save_lookups():
+ for template_dir in changes['template_paths']:
+ edxmako.paths.add_lookup('main', template_dir, prepend=True)
+
yield
else:
yield
diff --git a/openedx/core/djangoapps/theming/tests/test_helpers.py b/openedx/core/djangoapps/theming/tests/test_helpers.py
deleted file mode 100644
index c3eb2e6253..0000000000
--- a/openedx/core/djangoapps/theming/tests/test_helpers.py
+++ /dev/null
@@ -1,130 +0,0 @@
-"""Tests of comprehensive theming."""
-import unittest
-from mock import patch
-
-from django.test import TestCase, RequestFactory
-from django.conf import settings
-
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme
-from openedx.core.djangoapps.theming.helpers import get_template_path_with_theme, strip_site_theme_templates_path, \
- get_current_site_theme_dir
-
-
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-class TestHelpersLMS(TestCase):
- """Test comprehensive theming helper functions."""
-
- def setUp(self):
- super(TestHelpersLMS, self).setUp()
-
- @with_comprehensive_theme('red-theme')
- def test_get_template_path_with_theme_enabled(self):
- """
- Tests template paths are returned from enabled theme.
- """
- template_path = get_template_path_with_theme('header.html')
- self.assertEqual(template_path, '/red-theme/lms/templates/header.html')
-
- @with_comprehensive_theme('red-theme')
- def test_get_template_path_with_theme_for_missing_template(self):
- """
- Tests default template paths are returned if template is not found in the theme.
- """
- template_path = get_template_path_with_theme('course.html')
- self.assertEqual(template_path, 'course.html')
-
- def test_get_template_path_with_theme_disabled(self):
- """
- Tests default template paths are returned when theme is non theme is enabled.
- """
- template_path = get_template_path_with_theme('header.html')
- self.assertEqual(template_path, 'header.html')
-
- @with_comprehensive_theme('red-theme')
- def test_strip_site_theme_templates_path_theme_enabled(self):
- """
- Tests site theme templates path is stripped from the given template path.
- """
- template_path = strip_site_theme_templates_path('/red-theme/lms/templates/header.html')
- self.assertEqual(template_path, 'header.html')
-
- def test_strip_site_theme_templates_path_theme_disabled(self):
- """
- Tests site theme templates path returned unchanged if no theme is applied.
- """
- template_path = strip_site_theme_templates_path('/red-theme/lms/templates/header.html')
- self.assertEqual(template_path, '/red-theme/lms/templates/header.html')
-
- @with_comprehensive_theme('red-theme')
- def test_get_current_site_theme_dir(self):
- """
- Tests current site theme name.
- """
- factory = RequestFactory()
- with patch(
- 'edxmako.middleware.REQUEST_CONTEXT.request',
- factory.get('/', SERVER_NAME="red-theme.org"),
- create=True,
- ):
- current_site = get_current_site_theme_dir()
- self.assertEqual(current_site, 'red-theme')
-
-
-@unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
-class TestHelpersCMS(TestCase):
- """Test comprehensive theming helper functions."""
-
- def setUp(self):
- super(TestHelpersCMS, self).setUp()
-
- @with_comprehensive_theme('red-theme')
- def test_get_template_path_with_theme_enabled(self):
- """
- Tests template paths are returned from enabled theme.
- """
- template_path = get_template_path_with_theme('login.html')
- self.assertEqual(template_path, '/red-theme/cms/templates/login.html')
-
- @with_comprehensive_theme('red-theme')
- def test_get_template_path_with_theme_for_missing_template(self):
- """
- Tests default template paths are returned if template is not found in the theme.
- """
- template_path = get_template_path_with_theme('certificates.html')
- self.assertEqual(template_path, 'certificates.html')
-
- def test_get_template_path_with_theme_disabled(self):
- """
- Tests default template paths are returned when theme is non theme is enabled.
- """
- template_path = get_template_path_with_theme('login.html')
- self.assertEqual(template_path, 'login.html')
-
- @with_comprehensive_theme('red-theme')
- def test_strip_site_theme_templates_path_theme_enabled(self):
- """
- Tests site theme templates path is stripped from the given template path.
- """
- template_path = strip_site_theme_templates_path('/red-theme/cms/templates/login.html')
- self.assertEqual(template_path, 'login.html')
-
- def test_strip_site_theme_templates_path_theme_disabled(self):
- """
- Tests site theme templates path returned unchanged if no theme is applied.
- """
- template_path = strip_site_theme_templates_path('/red-theme/cms/templates/login.html')
- self.assertEqual(template_path, '/red-theme/cms/templates/login.html')
-
- @with_comprehensive_theme('red-theme')
- def test_get_current_site_theme_dir(self):
- """
- Tests current site theme name.
- """
- factory = RequestFactory()
- with patch(
- 'edxmako.middleware.REQUEST_CONTEXT.request',
- factory.get('/', SERVER_NAME="red-theme.org"),
- create=True,
- ):
- current_site = get_current_site_theme_dir()
- self.assertEqual(current_site, 'red-theme')
diff --git a/openedx/core/djangoapps/theming/tests/test_storage.py b/openedx/core/djangoapps/theming/tests/test_storage.py
deleted file mode 100644
index b7ede7160d..0000000000
--- a/openedx/core/djangoapps/theming/tests/test_storage.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""
-Tests for comprehensive theme static files storage classes.
-"""
-import ddt
-import unittest
-import re
-
-from mock import patch
-
-from django.test import TestCase
-from django.conf import settings
-
-from openedx.core.djangoapps.theming.helpers import get_base_theme_dir
-from openedx.core.djangoapps.theming.storage import ComprehensiveThemingStorage
-
-
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-@ddt.ddt
-class TestStorageLMS(TestCase):
- """
- Test comprehensive theming static files storage.
- """
-
- def setUp(self):
- super(TestStorageLMS, self).setUp()
- self.themes_dir = get_base_theme_dir()
- self.enabled_theme = "red-theme"
- self.system_dir = settings.REPO_ROOT / "lms"
- self.storage = ComprehensiveThemingStorage(location=self.themes_dir)
-
- @ddt.data(
- (True, "images/logo.png"),
- (True, "images/favicon.ico"),
- (False, "images/spinning.gif"),
- )
- @ddt.unpack
- def test_themed(self, is_themed, asset):
- """
- Verify storage returns True on themed assets
- """
- self.assertEqual(is_themed, self.storage.themed(asset, self.enabled_theme))
-
- @ddt.data(
- ("images/logo.png", ),
- ("images/favicon.ico", ),
- )
- @ddt.unpack
- def test_url(self, asset):
- """
- Verify storage returns correct url depending upon the enabled theme
- """
- with patch(
- "openedx.core.djangoapps.theming.storage.get_current_site_theme_dir",
- return_value=self.enabled_theme,
- ):
- asset_url = self.storage.url(asset)
- # remove hash key from file url
- asset_url = re.sub(r"(\.\w+)(\.png|\.ico)$", r"\g<2>", asset_url)
- expected_url = self.storage.base_url + self.enabled_theme + "/" + asset
-
- self.assertEqual(asset_url, expected_url)
-
- @ddt.data(
- ("images/logo.png", ),
- ("images/favicon.ico", ),
- )
- @ddt.unpack
- def test_path(self, asset):
- """
- Verify storage returns correct file path depending upon the enabled theme
- """
- with patch(
- "openedx.core.djangoapps.theming.storage.get_current_site_theme_dir",
- return_value=self.enabled_theme,
- ):
- asset_url = self.storage.url(asset)
- asset_url = asset_url.replace(self.storage.base_url, "")
- # remove hash key from file url
- asset_url = re.sub(r"(\.\w+)(\.png|\.ico)$", r"\g<2>", asset_url)
- returned_path = self.storage.path(asset_url)
- expected_path = self.themes_dir / self.enabled_theme / "lms/static/" / asset
-
- self.assertEqual(expected_path, returned_path)
diff --git a/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py b/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py
deleted file mode 100644
index b7852249d9..0000000000
--- a/openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py
+++ /dev/null
@@ -1,235 +0,0 @@
-"""
- Tests for comprehensive themes.
-"""
-import unittest
-
-from django.conf import settings
-from django.test import TestCase, override_settings
-from django.contrib import staticfiles
-
-from paver.easy import call_task
-
-from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme
-
-
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-class TestComprehensiveThemeLMS(TestCase):
- """
- Test html, sass and static file overrides for comprehensive themes.
- """
-
- def setUp(self):
- """
- Clear static file finders cache and register cleanup methods.
- """
- super(TestComprehensiveThemeLMS, self).setUp()
-
- # Clear the internal staticfiles caches, to get test isolation.
- staticfiles.finders.get_finder.cache_clear()
-
- @classmethod
- def setUpClass(cls):
- """
- Enable Comprehensive theme and compile sass files.
- """
- # Apply Comprehensive theme and compile sass assets.
- compile_sass('lms')
-
- super(TestComprehensiveThemeLMS, cls).setUpClass()
-
- @override_settings(COMPREHENSIVE_THEME_DIR=settings.TEST_THEME.dirname())
- @with_comprehensive_theme(settings.TEST_THEME.basename())
- def test_footer(self):
- """
- Test that theme footer is used instead of default footer.
- """
- resp = self.client.get('/')
- self.assertEqual(resp.status_code, 200)
- # This string comes from header.html of test-theme
- self.assertContains(resp, "This is a footer for test-theme.")
-
- @override_settings(COMPREHENSIVE_THEME_DIR=settings.TEST_THEME.dirname())
- @with_comprehensive_theme(settings.TEST_THEME.basename())
- def test_logo_image(self):
- """
- Test that theme logo is used instead of default logo.
- """
- result = staticfiles.finders.find('test-theme/images/logo.png')
- self.assertEqual(result, settings.TEST_THEME / 'lms/static/images/logo.png')
-
- @override_settings(COMPREHENSIVE_THEME_DIR=settings.TEST_THEME.dirname())
- @with_comprehensive_theme(settings.TEST_THEME.basename())
- def test_css_files(self):
- """
- Test that theme sass files are used instead of default sass files.
- """
- result = staticfiles.finders.find('test-theme/css/lms-main.css')
- self.assertEqual(result, settings.TEST_THEME / "lms/static/css/lms-main.css")
-
- lms_main_css = ""
- with open(result) as css_file:
- lms_main_css += css_file.read()
-
- self.assertIn("background:#00fa00", lms_main_css)
-
-
-@unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
-class TestComprehensiveThemeCMS(TestCase):
- """
- Test html, sass and static file overrides for comprehensive themes.
- """
-
- def setUp(self):
- """
- Clear static file finders cache and register cleanup methods.
- """
- super(TestComprehensiveThemeCMS, self).setUp()
-
- # Clear the internal staticfiles caches, to get test isolation.
- staticfiles.finders.get_finder.cache_clear()
-
- @classmethod
- def setUpClass(cls):
- """
- Enable Comprehensive theme and compile sass files.
- """
- # Apply Comprehensive theme and compile sass assets.
- compile_sass('cms')
-
- super(TestComprehensiveThemeCMS, cls).setUpClass()
-
- @override_settings(COMPREHENSIVE_THEME_DIR=settings.TEST_THEME.dirname())
- @with_comprehensive_theme(settings.TEST_THEME.basename())
- def test_template_override(self):
- """
- Test that theme templates are used instead of default templates.
- """
- resp = self.client.get('/signin')
- self.assertEqual(resp.status_code, 200)
- # This string comes from login.html of test-theme
- self.assertContains(resp, "Login Page override for test-theme.")
-
- @override_settings(COMPREHENSIVE_THEME_DIR=settings.TEST_THEME.dirname())
- @with_comprehensive_theme(settings.TEST_THEME.basename())
- def test_css_files(self):
- """
- Test that theme sass files are used instead of default sass files.
- """
- result = staticfiles.finders.find('test-theme/css/studio-main.css')
- self.assertEqual(result, settings.TEST_THEME / "cms/static/css/studio-main.css")
-
- cms_main_css = ""
- with open(result) as css_file:
- cms_main_css += css_file.read()
-
- self.assertIn("background:#00fa00", cms_main_css)
-
-
-@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-class TestComprehensiveThemeDisabledLMS(TestCase):
- """
- Test Sass compilation order and sass overrides for comprehensive themes.
- """
-
- def setUp(self):
- """
- Clear static file finders cache.
- """
- super(TestComprehensiveThemeDisabledLMS, self).setUp()
-
- # Clear the internal staticfiles caches, to get test isolation.
- staticfiles.finders.get_finder.cache_clear()
-
- @classmethod
- def setUpClass(cls):
- """
- Compile sass files.
- """
- # compile LMS SASS
- compile_sass('lms')
-
- super(TestComprehensiveThemeDisabledLMS, cls).setUpClass()
-
- def test_logo(self):
- """
- Test that default logo is picked in case of no comprehensive theme.
- """
- result = staticfiles.finders.find('images/logo.png')
- self.assertEqual(result, settings.REPO_ROOT / 'lms/static/images/logo.png')
-
- def test_css(self):
- """
- Test that default css files served without comprehensive themes applied.
- """
- result = staticfiles.finders.find('css/lms-main.css')
- self.assertEqual(result, settings.REPO_ROOT / "lms/static/css/lms-main.css")
-
- lms_main_css = ""
- with open(result) as css_file:
- lms_main_css += css_file.read()
-
- self.assertNotIn("background:#00fa00", lms_main_css)
-
-
-@unittest.skipUnless(settings.ROOT_URLCONF == 'cms.urls', 'Test only valid in cms')
-class TestComprehensiveThemeDisabledCMS(TestCase):
- """
- Test default html, sass and static file when no theme is applied.
- """
-
- def setUp(self):
- """
- Clear static file finders cache and register cleanup methods.
- """
- super(TestComprehensiveThemeDisabledCMS, self).setUp()
-
- # Clear the internal staticfiles caches, to get test isolation.
- staticfiles.finders.get_finder.cache_clear()
-
- @classmethod
- def setUpClass(cls):
- """
- Enable Comprehensive theme and compile sass files.
- """
- # Apply Comprehensive theme and compile sass assets.
- compile_sass('cms')
-
- super(TestComprehensiveThemeDisabledCMS, cls).setUpClass()
-
- def test_template_override(self):
- """
- Test that defaults templates are used when no theme is applied.
- """
- resp = self.client.get('/signin')
- self.assertEqual(resp.status_code, 200)
- self.assertNotContains(resp, "Login Page override for test-theme.")
-
- def test_css_files(self):
- """
- Test that default css files served without comprehensive themes applied..
- """
- result = staticfiles.finders.find('css/studio-main.css')
- self.assertEqual(result, settings.REPO_ROOT / "cms/static/css/studio-main.css")
-
- cms_main_css = ""
- with open(result) as css_file:
- cms_main_css += css_file.read()
-
- self.assertNotIn("background:#00fa00", cms_main_css)
-
-
-def compile_sass(system):
- """
- Process xmodule assets and compile sass files for the given system.
-
- :param system - 'lms' or 'cms', specified the system to compile sass for.
- """
- # Compile system sass files
- call_task(
- 'pavelib.assets.update_assets',
- args=(
- system,
- "--themes_dir={}".format(settings.TEST_THEME.dirname()),
- "--themes={}".format(settings.TEST_THEME.basename()),
- "--settings=test"),
- )
diff --git a/openedx/core/lib/tempdir.py b/openedx/core/lib/tempdir.py
index de8f26bc47..8d440ad14c 100644
--- a/openedx/core/lib/tempdir.py
+++ b/openedx/core/lib/tempdir.py
@@ -17,22 +17,3 @@ def cleanup_tempdir(the_dir):
"""Called on process exit to remove a temp directory."""
if os.path.exists(the_dir):
shutil.rmtree(the_dir)
-
-
-def mksym_link(src, dest):
- """
- Creates a symbolic link which will be deleted when the process ends.
- :param src: path to source
- :param dest: path to destination
- """
- os.symlink(src, dest)
- atexit.register(cleanup_symlink, dest)
-
-
-def cleanup_symlink(link_path):
- """
- Removes symbolic link for
- :param link_path:
- """
- if os.path.exists(link_path):
- os.remove(link_path)
diff --git a/openedx/core/storage.py b/openedx/core/storage.py
index 214ef1e8fd..ff17694707 100644
--- a/openedx/core/storage.py
+++ b/openedx/core/storage.py
@@ -1,16 +1,15 @@
"""
Django storage backends for Open edX.
"""
-from django.contrib.staticfiles.storage import StaticFilesStorage
+from django.contrib.staticfiles.storage import StaticFilesStorage, CachedFilesMixin
from pipeline.storage import PipelineMixin, NonPackagingMixin
from require.storage import OptimizedFilesMixin
-from openedx.core.djangoapps.theming.storage import ComprehensiveThemingStorage
class ProductionStorage(
- ComprehensiveThemingStorage,
OptimizedFilesMixin,
PipelineMixin,
+ CachedFilesMixin,
StaticFilesStorage
):
"""
@@ -21,7 +20,6 @@ class ProductionStorage(
class DevelopmentStorage(
- ComprehensiveThemingStorage,
NonPackagingMixin,
PipelineMixin,
StaticFilesStorage
diff --git a/pavelib/assets.py b/pavelib/assets.py
index 43e22bbe2a..26478f1dac 100644
--- a/pavelib/assets.py
+++ b/pavelib/assets.py
@@ -20,213 +20,73 @@ from .utils.cmd import cmd, django_cmd
ALL_SYSTEMS = ['lms', 'studio']
COFFEE_DIRS = ['lms', 'cms', 'common']
-
-LMS = 'lms'
-CMS = 'cms'
-
-SYSTEMS = {
- 'lms': LMS,
- 'cms': CMS,
- 'studio': CMS
-}
-
-# Common lookup paths that are added to the lookup paths for all sass compilations
-COMMON_LOOKUP_DIRS = [
- path("common/static"),
+# A list of directories. Each will be paired with a sibling /css directory.
+COMMON_SASS_DIRECTORIES = [
path("common/static/sass"),
]
-
-# system specific lookup path additions, add sass dirs if one system depends on the sass files for other systems
-SASS_LOOKUP_DEPENDENCIES = {
- 'cms': [path('lms') / 'static' / 'sass' / 'partials', ],
-}
+LMS_SASS_DIRECTORIES = [
+ path("lms/static/sass"),
+ path("lms/static/themed_sass"),
+ path("lms/static/certificates/sass"),
+]
+CMS_SASS_DIRECTORIES = [
+ path("cms/static/sass"),
+]
+THEME_SASS_DIRECTORIES = []
+SASS_LOAD_PATHS = ['common/static', 'common/static/sass']
-def get_sass_directories(system, theme_dir=None):
+def configure_paths():
+ """Configure our paths based on settings. Called immediately."""
+ edxapp_env = Env()
+ if edxapp_env.feature_flags.get('USE_CUSTOM_THEME', False):
+ theme_name = edxapp_env.env_tokens.get('THEME_NAME', '')
+ parent_dir = path(edxapp_env.REPO_ROOT).abspath().parent
+ theme_root = parent_dir / "themes" / theme_name
+ COFFEE_DIRS.append(theme_root)
+ sass_dir = theme_root / "static" / "sass"
+ css_dir = theme_root / "static" / "css"
+ if sass_dir.isdir():
+ css_dir.mkdir_p()
+ THEME_SASS_DIRECTORIES.append(sass_dir)
+
+ if edxapp_env.env_tokens.get("COMPREHENSIVE_THEME_DIR", ""):
+ theme_dir = path(edxapp_env.env_tokens["COMPREHENSIVE_THEME_DIR"])
+ lms_sass = theme_dir / "lms" / "static" / "sass"
+ lms_css = theme_dir / "lms" / "static" / "css"
+ if lms_sass.isdir():
+ lms_css.mkdir_p()
+ THEME_SASS_DIRECTORIES.append(lms_sass)
+ cms_sass = theme_dir / "cms" / "static" / "sass"
+ cms_css = theme_dir / "cms" / "static" / "css"
+ if cms_sass.isdir():
+ cms_css.mkdir_p()
+ THEME_SASS_DIRECTORIES.append(cms_sass)
+
+configure_paths()
+
+
+def applicable_sass_directories(systems=None):
"""
- Determine the set of SASS directories to be compiled for the specified list of system and theme
- and return a list of those directories.
+ Determine the applicable set of SASS directories to be
+ compiled for the specified list of systems.
- Each item in the list is dict object containing the following key-value pairs.
- {
- "sass_source_dir": "", # directory where source sass files are present
- "css_destination_dir": "", # destination where css files would be placed
- "lookup_paths": [], # list of directories to be passed as lookup paths for @import resolution.
- }
+ Args:
+ systems: A list of systems (defaults to all)
- if theme_dir is empty or None then return sass directories for the given system only. (i.e. lms or cms)
-
- :param system: name if the system for which to compile sass e.g. 'lms', 'cms'
- :param theme_dir: absolute path of theme for which to compile sass files.
- """
- if system not in SYSTEMS:
- raise ValueError("'system' must be one of ({allowed_values})".format(allowed_values=', '.join(SYSTEMS.keys())))
- system = SYSTEMS[system]
-
- applicable_directories = list()
-
- if theme_dir:
- # Add theme sass directories
- applicable_directories.extend(
- get_theme_sass_dirs(system, theme_dir)
- )
- else:
- # add system sass directories
- applicable_directories.extend(
- get_system_sass_dirs(system)
- )
-
- return applicable_directories
-
-
-def get_common_sass_directories():
- """
- Determine the set of common SASS directories to be compiled for all the systems and themes.
-
- Each item in the returned list is dict object containing the following key-value pairs.
- {
- "sass_source_dir": "", # directory where source sass files are present
- "css_destination_dir": "", # destination where css files would be placed
- "lookup_paths": [], # list of directories to be passed as lookup paths for @import resolution.
- }
- """
- applicable_directories = list()
-
- # add common sass directories
- applicable_directories.append({
- "sass_source_dir": path("common/static/sass"),
- "css_destination_dir": path("common/static/css"),
- "lookup_paths": [
- path("common/static"),
- path("common/static/sass"),
- ],
- })
-
- return applicable_directories
-
-
-def get_theme_sass_dirs(system, theme_dir):
- """
- Return list of sass dirs that need to be compiled for the given theme.
-
- :param system: name if the system for which to compile sass e.g. 'lms', 'cms'
- :param theme_dir: absolute path of theme for which to compile sass files.
- """
- if system not in ('lms', 'cms'):
- raise ValueError('"system" must either be "lms" or "cms"')
-
- dirs = []
-
- system_sass_dir = path(system) / "static" / "sass"
- sass_dir = theme_dir / system / "static" / "sass"
- css_dir = theme_dir / system / "static" / "css"
-
- dependencies = SASS_LOOKUP_DEPENDENCIES.get(system, [])
- if sass_dir.isdir():
- css_dir.mkdir_p()
-
- # first compile lms sass files and place css in theme dir
- dirs.append({
- "sass_source_dir": system_sass_dir,
- "css_destination_dir": css_dir,
- "lookup_paths": dependencies + [
- sass_dir / "partials",
- system_sass_dir / "partials",
- system_sass_dir,
- ],
- })
-
- # now compile theme sass files and override css files generated from lms
- dirs.append({
- "sass_source_dir": sass_dir,
- "css_destination_dir": css_dir,
- "lookup_paths": dependencies + [
- sass_dir / "partials",
- system_sass_dir / "partials",
- system_sass_dir,
- ],
- })
-
- return dirs
-
-
-def get_system_sass_dirs(system):
- """
- Return list of sass dirs that need to be compiled for the given system.
-
- :param system: name if the system for which to compile sass e.g. 'lms', 'cms'
- """
- if system not in ('lms', 'cms'):
- raise ValueError('"system" must either be "lms" or "cms"')
-
- dirs = []
- sass_dir = path(system) / "static" / "sass"
- css_dir = path(system) / "static" / "css"
-
- dependencies = SASS_LOOKUP_DEPENDENCIES.get(system, [])
- dirs.append({
- "sass_source_dir": sass_dir,
- "css_destination_dir": css_dir,
- "lookup_paths": dependencies + [
- sass_dir / "partials",
- sass_dir,
- ],
- })
-
- if system == 'lms':
- dirs.append({
- "sass_source_dir": path(system) / "static" / "certificates" / "sass",
- "css_destination_dir": path(system) / "static" / "certificates" / "css",
- "lookup_paths": [
- sass_dir / "partials",
- sass_dir
- ],
- })
-
- return dirs
-
-
-def get_watcher_dirs(themes_base_dir=None, themes=None):
- """
- Return sass directories that need to be added to sass watcher.
-
- Example:
- >> get_watcher_dirs('/edx/app/edx-platform/themes', ['red-theme'])
- [
- 'common/static',
- 'common/static/sass',
- 'lms/static/sass',
- 'lms/static/sass/partials',
- '/edx/app/edxapp/edx-platform/themes/red-theme/lms/static/sass',
- '/edx/app/edxapp/edx-platform/themes/red-theme/lms/static/sass/partials',
- 'cms/static/sass',
- 'cms/static/sass/partials',
- '/edx/app/edxapp/edx-platform/themes/red-theme/cms/static/sass/partials',
- ]
-
- Parameters:
- themes_base_dir (str): base directory that contains all the themes.
- themes (list): list containing names of themes
Returns:
- (list): dirs that need to be added to sass watchers.
+ A list of SASS directories to be compiled.
"""
- dirs = []
- dirs.extend(COMMON_LOOKUP_DIRS)
- if themes_base_dir and themes:
- # Register sass watchers for all the given themes
- theme_dirs = [(path(themes_base_dir) / theme) for theme in themes if theme]
- for theme_dir in theme_dirs:
- for _dir in get_sass_directories('lms', theme_dir) + get_sass_directories('cms', theme_dir):
- dirs.append(_dir['sass_source_dir'])
- dirs.extend(_dir['lookup_paths'])
- # Register sass watchers for lms and cms
- for _dir in get_sass_directories('lms') + get_sass_directories('cms') + get_common_sass_directories():
- dirs.append(_dir['sass_source_dir'])
- dirs.extend(_dir['lookup_paths'])
-
- # remove duplicates
- dirs = list(set(dirs))
- return dirs
+ if not systems:
+ systems = ALL_SYSTEMS
+ applicable_directories = []
+ applicable_directories.extend(COMMON_SASS_DIRECTORIES)
+ if "lms" in systems:
+ applicable_directories.extend(LMS_SASS_DIRECTORIES)
+ if "studio" in systems or "cms" in systems:
+ applicable_directories.extend(CMS_SASS_DIRECTORIES)
+ applicable_directories.extend(THEME_SASS_DIRECTORIES)
+ return applicable_directories
class CoffeeScriptWatcher(PatternMatchingEventHandler):
@@ -262,15 +122,11 @@ class SassWatcher(PatternMatchingEventHandler):
patterns = ['*.scss']
ignore_patterns = ['common/static/xmodule/*']
- def register(self, observer, directories):
+ def register(self, observer):
"""
register files with observer
-
- Arguments:
- observer (watchdog.observers.Observer): sass file observer
- directories (list): list of directories to be register for sass watcher.
"""
- for dirname in directories:
+ for dirname in SASS_LOAD_PATHS + applicable_sass_directories():
paths = []
if '*' in dirname:
paths.extend(glob.glob(dirname))
@@ -294,6 +150,12 @@ class XModuleSassWatcher(SassWatcher):
ignore_directories = True
ignore_patterns = []
+ def register(self, observer):
+ """
+ register files with observer
+ """
+ observer.schedule(self, 'common/lib/xmodule/', recursive=True)
+
def on_modified(self, event):
print('\tCHANGED:', event.src_path)
try:
@@ -351,125 +213,12 @@ def compile_coffeescript(*files):
@no_help
@cmdopts([
('system=', 's', 'The system to compile sass for (defaults to all)'),
- ('themes_dir=', '-td', 'The themes dir containing all themes (defaults to None)'),
- ('themes=', '-t', 'The theme to compile sass for (defaults to None)'),
('debug', 'd', 'Debug mode'),
('force', '', 'Force full compilation'),
])
def compile_sass(options):
"""
- Compile Sass to CSS. If command is called without any arguments, it will
- only compile lms, cms sass for the open source theme. And none of the comprehensive theme's sass would be compiled.
-
- If you want to compile sass for all comprehensive themes you will have to run compile_sass
- specifying all the themes that need to be compiled..
-
- The following is a list of some possible ways to use this command.
-
- Command:
- paver compile_sass
- Description:
- compile sass files for both lms and cms. If command is called like above (i.e. without any arguments) it will
- only compile lms, cms sass for the open source theme. None of the theme's sass will be compiled.
-
- Command:
- paver compile_sass --themes_dir=/edx/app/edxapp/edx-platform/themes --themes=red-theme
- Description:
- compile sass files for both lms and cms for 'red-theme' present in '/edx/app/edxapp/edx-platform/themes'
-
- Command:
- paver compile_sass --themes_dir=/edx/app/edxapp/edx-platform/themes --themes=red-theme,stanford-style
- Description:
- compile sass files for both lms and cms for 'red-theme' and 'stanford-style' present in
- '/edx/app/edxapp/edx-platform/themes'.
-
- Command:
- paver compile_sass --system=cms --themes_dir=/edx/app/edxapp/edx-platform/themes
- --themes=red-theme,stanford-style
- Description:
- compile sass files for cms only for 'red-theme' and 'stanford-style' present in
- '/edx/app/edxapp/edx-platform/themes'.
-
- """
- debug = options.get('debug')
- force = options.get('force')
- systems = getattr(options, 'system', ALL_SYSTEMS)
- themes = getattr(options, 'themes', None)
- themes_dir = getattr(options, 'themes_dir', None)
-
- if not themes_dir and themes:
- # We can not compile a theme sass without knowing the directory that contains the theme.
- raise ValueError('themes_dir must be provided for compiling theme sass.')
- else:
- theme_base_dir = path(themes_dir)
-
- if isinstance(systems, basestring):
- systems = systems.split(',')
- else:
- systems = systems if isinstance(systems, list) else [systems]
- if isinstance(themes, basestring):
- themes = themes.split(',')
- else:
- themes = themes if isinstance(themes, list) else [themes]
-
- # Compile sass for OpenEdx theme after comprehensive themes
- if None not in themes:
- themes.append(None)
-
- timing_info = []
- dry_run = tasks.environment.dry_run
- compilation_results = {'success': [], 'failure': []}
-
- print("\t\tStarted compiling Sass:")
-
- # compile common sass files
- is_successful = _compile_sass('common', None, debug, force, timing_info)
- if is_successful:
- print("Finished compiling 'common' sass.")
- compilation_results['success' if is_successful else 'failure'].append('"common" sass files.')
-
- for system in systems:
- for theme in themes:
- print("Started compiling '{system}' Sass for '{theme}'.".format(system=system, theme=theme or 'system'))
-
- # Compile sass files
- is_successful = _compile_sass(
- system=system,
- theme=theme_base_dir / theme if theme_base_dir and theme else None,
- debug=debug,
- force=force,
- timing_info=timing_info
- )
-
- if is_successful:
- print("Finished compiling '{system}' Sass for '{theme}'.".format(
- system=system, theme=theme or 'system'
- ))
-
- compilation_results['success' if is_successful else 'failure'].append('{system} sass for {theme}.'.format(
- system=system, theme=theme or 'system',
- ))
-
- print("\t\tFinished compiling Sass:")
- if not dry_run:
- for sass_dir, css_dir, duration in timing_info:
- print(">> {} -> {} in {}s".format(sass_dir, css_dir, duration))
-
- if compilation_results['success']:
- print("\033[92m\n\nSuccessful compilations:\n--- " + "\n--- ".join(compilation_results['success']) + "\033[00m")
- if compilation_results['failure']:
- print("\033[91m\n\nFailed compilations:\n--- " + "\n--- ".join(compilation_results['failure']) + "\033[00m")
-
-
-def _compile_sass(system, theme, debug, force, timing_info):
- """
- Compile sass files for the given system and theme.
-
- :param system: system to compile sass for e.g. 'lms', 'cms', 'common'
- :param theme: absolute path of the theme to compile sass for.
- :param debug: boolean showing whether to display source comments in resulted css
- :param force: boolean showing whether to remove existing css files before generating new files
- :param timing_info: list variable to keep track of timing for sass compilation
+ Compile Sass to CSS.
"""
# Note: import sass only when it is needed and not at the top of the file.
@@ -477,14 +226,12 @@ def _compile_sass(system, theme, debug, force, timing_info):
# installed. In particular, this allows the install_prereqs command to be
# used to install the dependency.
import sass
- if system == "common":
- sass_dirs = get_common_sass_directories()
- else:
- sass_dirs = get_sass_directories(system, theme)
- dry_run = tasks.environment.dry_run
-
- # determine css out put style and source comments enabling
+ debug = options.get('debug')
+ force = options.get('force')
+ systems = getattr(options, 'system', ALL_SYSTEMS)
+ if isinstance(systems, basestring):
+ systems = systems.split(',')
if debug:
source_comments = True
output_style = 'nested'
@@ -492,18 +239,13 @@ def _compile_sass(system, theme, debug, force, timing_info):
source_comments = False
output_style = 'compressed'
- for dirs in sass_dirs:
+ timing_info = []
+ system_sass_directories = applicable_sass_directories(systems)
+ all_sass_directories = applicable_sass_directories()
+ dry_run = tasks.environment.dry_run
+ for sass_dir in system_sass_directories:
start = datetime.now()
- css_dir = dirs['css_destination_dir']
- sass_source_dir = dirs['sass_source_dir']
- lookup_paths = dirs['lookup_paths']
-
- if not sass_source_dir.isdir():
- print("\033[91m Sass dir '{dir}' does not exists, skipping sass compilation for '{theme}' \033[00m".format(
- dir=sass_dirs, theme=theme or system,
- ))
- # theme doesn't override sass directory, so skip it
- continue
+ css_dir = sass_dir.parent / "css"
if force:
if dry_run:
@@ -515,18 +257,22 @@ def _compile_sass(system, theme, debug, force, timing_info):
if dry_run:
tasks.environment.info("libsass {sass_dir}".format(
- sass_dir=sass_source_dir,
+ sass_dir=sass_dir,
))
else:
sass.compile(
- dirname=(sass_source_dir, css_dir),
- include_paths=COMMON_LOOKUP_DIRS + lookup_paths,
+ dirname=(sass_dir, css_dir),
+ include_paths=SASS_LOAD_PATHS + all_sass_directories,
source_comments=source_comments,
output_style=output_style,
)
duration = datetime.now() - start
- timing_info.append((sass_source_dir, css_dir, duration))
- return True
+ timing_info.append((sass_dir, css_dir, duration))
+
+ print("\t\tFinished compiling Sass:")
+ if not dry_run:
+ for sass_dir, css_dir, duration in timing_info:
+ print(">> {} -> {} in {}s".format(sass_dir, css_dir, duration))
def compile_templated_sass(systems, settings):
@@ -578,11 +324,7 @@ def collect_assets(systems, settings):
@task
-@cmdopts([
- ('background', 'b', 'Background mode'),
- ('themes_dir=', '-td', 'The themes dir containing all themes (defaults to None)'),
- ('themes=', '-t', 'The themes to add sass watchers for (defaults to None)'),
-])
+@cmdopts([('background', 'b', 'Background mode')])
def watch_assets(options):
"""
Watch for changes to asset files, and regenerate js/css
@@ -591,25 +333,11 @@ def watch_assets(options):
if tasks.environment.dry_run:
return
- themes = getattr(options, 'themes', None)
- themes_dir = getattr(options, 'themes_dir', None)
- if not themes_dir and themes:
- # We can not add theme sass watchers without knowing the directory that contains the themes.
- raise ValueError('themes_dir must be provided for compiling theme sass.')
- else:
- theme_base_dir = path(themes_dir)
-
- if isinstance(themes, basestring):
- themes = themes.split(',')
- else:
- themes = themes if isinstance(themes, list) else [themes]
-
- sass_directories = get_watcher_dirs(theme_base_dir, themes)
observer = Observer()
CoffeeScriptWatcher().register(observer)
- SassWatcher().register(observer, sass_directories)
- XModuleSassWatcher().register(observer, ['common/lib/xmodule/'])
+ SassWatcher().register(observer)
+ XModuleSassWatcher().register(observer)
XModuleAssetsWatcher().register(observer)
print("Starting asset watcher...")
@@ -655,30 +383,15 @@ def update_assets(args):
'--watch', action='store_true', default=False,
help="Watch files for changes",
)
- parser.add_argument(
- '--themes_dir', type=str, default=None,
- help="base directory where themes are placed",
- )
- parser.add_argument(
- '--themes', type=str, nargs='*', default=None,
- help="list of themes to compile sass for",
- )
args = parser.parse_args(args)
compile_templated_sass(args.system, args.settings)
process_xmodule_assets()
compile_coffeescript()
-
- call_task(
- 'pavelib.assets.compile_sass',
- options={'system': args.system, 'debug': args.debug, 'themes_dir': args.themes_dir, 'themes': args.themes},
- )
+ call_task('pavelib.assets.compile_sass', options={'system': args.system, 'debug': args.debug})
if args.collect:
collect_assets(args.system, args.settings)
if args.watch:
- call_task(
- 'pavelib.assets.watch_assets',
- options={'background': not args.debug, 'themes_dir': args.themes_dir, 'themes': args.themes},
- )
+ call_task('pavelib.assets.watch_assets', options={'background': not args.debug})
diff --git a/pavelib/paver_tests/test_assets.py b/pavelib/paver_tests/test_assets.py
index d94bf75e45..b690b6d0fe 100644
--- a/pavelib/paver_tests/test_assets.py
+++ b/pavelib/paver_tests/test_assets.py
@@ -1,16 +1,9 @@
"""Unit tests for the Paver asset tasks."""
import ddt
-import os
-from unittest import TestCase
from paver.easy import call_task
-from paver.easy import path
-from mock import patch
-from watchdog.observers import Observer
-from .utils import PaverTestCase
-ROOT_PATH = path(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
-TEST_THEME = ROOT_PATH / "common/test/test-theme" # pylint: disable=invalid-name
+from .utils import PaverTestCase
@ddt.ddt
@@ -48,11 +41,13 @@ class TestPaverAssetTasks(PaverTestCase):
if force:
expected_messages.append("rm -rf common/static/css/*.css")
expected_messages.append("libsass common/static/sass")
-
if "lms" in system:
if force:
expected_messages.append("rm -rf lms/static/css/*.css")
expected_messages.append("libsass lms/static/sass")
+ if force:
+ expected_messages.append("rm -rf lms/static/css/*.css")
+ expected_messages.append("libsass lms/static/themed_sass")
if force:
expected_messages.append("rm -rf lms/static/certificates/css/*.css")
expected_messages.append("libsass lms/static/certificates/sass")
@@ -60,145 +55,4 @@ class TestPaverAssetTasks(PaverTestCase):
if force:
expected_messages.append("rm -rf cms/static/css/*.css")
expected_messages.append("libsass cms/static/sass")
-
self.assertEquals(self.task_messages, expected_messages)
-
-
-@ddt.ddt
-class TestPaverThemeAssetTasks(PaverTestCase):
- """
- Test the Paver asset tasks.
- """
-
- @ddt.data(
- [""],
- ["--force"],
- ["--debug"],
- ["--system=lms"],
- ["--system=lms --force"],
- ["--system=studio"],
- ["--system=studio --force"],
- ["--system=lms,studio"],
- ["--system=lms,studio --force"],
- )
- @ddt.unpack
- def test_compile_theme_sass(self, options):
- """
- Test the "compile_sass" task.
- """
- parameters = options.split(" ")
- system = []
-
- if "--system=studio" not in parameters:
- system += ["lms"]
- if "--system=lms" not in parameters:
- system += ["studio"]
- debug = "--debug" in parameters
- force = "--force" in parameters
-
- self.reset_task_messages()
- call_task(
- 'pavelib.assets.compile_sass',
- options={"system": system, "debug": debug, "force": force, "themes_dir": TEST_THEME.dirname(),
- "themes": [TEST_THEME.basename()]},
- )
- expected_messages = []
- if force:
- expected_messages.append("rm -rf common/static/css/*.css")
- expected_messages.append("libsass common/static/sass")
-
- if "lms" in system:
- expected_messages.append("mkdir_p " + repr(TEST_THEME / "lms/static/css"))
-
- if force:
- expected_messages.append("rm -rf " + str(TEST_THEME) + "/lms/static/css/*.css")
- expected_messages.append("libsass lms/static/sass")
- if force:
- expected_messages.append("rm -rf " + str(TEST_THEME) + "/lms/static/css/*.css")
- expected_messages.append("libsass " + str(TEST_THEME) + "/lms/static/sass")
- if force:
- expected_messages.append("rm -rf lms/static/css/*.css")
- expected_messages.append("libsass lms/static/sass")
- if force:
- expected_messages.append("rm -rf lms/static/certificates/css/*.css")
- expected_messages.append("libsass lms/static/certificates/sass")
-
- if "studio" in system:
- expected_messages.append("mkdir_p " + repr(TEST_THEME / "cms/static/css"))
- if force:
- expected_messages.append("rm -rf " + str(TEST_THEME) + "/cms/static/css/*.css")
- expected_messages.append("libsass cms/static/sass")
- if force:
- expected_messages.append("rm -rf " + str(TEST_THEME) + "/cms/static/css/*.css")
- expected_messages.append("libsass " + str(TEST_THEME) + "/cms/static/sass")
-
- if force:
- expected_messages.append("rm -rf cms/static/css/*.css")
- expected_messages.append("libsass cms/static/sass")
-
- self.assertEquals(self.task_messages, expected_messages)
-
-
-class TestPaverWatchAssetTasks(TestCase):
- """
- Test the Paver watch asset tasks.
- """
-
- def setUp(self):
- self.expected_sass_directories = [
- path('common/static/sass'),
- path('common/static'),
- path('lms/static/sass/partials'),
- path('lms/static/sass'),
- path('lms/static/certificates/sass'),
- path('cms/static/sass'),
- path('cms/static/sass/partials'),
- ]
- super(TestPaverWatchAssetTasks, self).setUp()
-
- def test_watch_assets(self):
- """
- Test the "compile_sass" task.
- """
- with patch('pavelib.assets.SassWatcher.register') as mock_register:
- with patch('pavelib.assets.Observer.start'):
- call_task(
- 'pavelib.assets.watch_assets',
- options={"background": True},
- )
- self.assertEqual(mock_register.call_count, 2)
-
- sass_watcher_args = mock_register.call_args_list[0][0]
-
- self.assertIsInstance(sass_watcher_args[0], Observer)
- self.assertIsInstance(sass_watcher_args[1], list)
- self.assertItemsEqual(sass_watcher_args[1], self.expected_sass_directories)
-
- def test_watch_theme_assets(self):
- """
- Test the Paver watch asset tasks with theming enabled.
- """
- self.expected_sass_directories.extend([
- path(TEST_THEME) / 'lms/static/sass',
- path(TEST_THEME) / 'lms/static/sass/partials',
- path(TEST_THEME) / 'cms/static/sass',
- path(TEST_THEME) / 'cms/static/sass/partials',
- ])
-
- with patch('pavelib.assets.SassWatcher.register') as mock_register:
- with patch('pavelib.assets.Observer.start'):
- call_task(
- 'pavelib.assets.watch_assets',
- options={"background": True, "themes_dir": TEST_THEME.dirname(),
- "themes": [TEST_THEME.basename()]},
- )
- self.assertEqual(mock_register.call_count, 2)
-
- sass_watcher_args = mock_register.call_args_list[0][0]
- self.assertIsInstance(sass_watcher_args[0], Observer)
- self.assertIsInstance(sass_watcher_args[1], list)
- self.assertItemsEqual(sass_watcher_args[1], self.expected_sass_directories)
-
- def tearDown(self):
- self.expected_sass_directories = []
- super(TestPaverWatchAssetTasks, self).tearDown()
diff --git a/pavelib/paver_tests/test_servers.py b/pavelib/paver_tests/test_servers.py
index 7ce1c38c32..d2169b7ac0 100644
--- a/pavelib/paver_tests/test_servers.py
+++ b/pavelib/paver_tests/test_servers.py
@@ -18,6 +18,7 @@ EXPECTED_COMMON_SASS_DIRECTORIES = [
]
EXPECTED_LMS_SASS_DIRECTORIES = [
"lms/static/sass",
+ "lms/static/themed_sass",
"lms/static/certificates/sass",
]
EXPECTED_CMS_SASS_DIRECTORIES = [
diff --git a/requirements/edx/github.txt b/requirements/edx/github.txt
index 4950e7982f..293ba31614 100644
--- a/requirements/edx/github.txt
+++ b/requirements/edx/github.txt
@@ -45,7 +45,7 @@
# Third-party:
git+https://github.com/cyberdelia/django-pipeline.git@1.5.3#egg=django-pipeline==1.5.3
-git+https://github.com/edx/django-wiki.git@v0.0.7#egg=django-wiki==0.0.7
+git+https://github.com/edx/django-wiki.git@v0.0.5#egg=django-wiki==0.0.5
git+https://github.com/edx/django-openid-auth.git@0.8#egg=django-openid-auth==0.8
git+https://github.com/edx/MongoDBProxy.git@25b99097615bda06bd7cdfe5669ed80dc2a7fed0#egg=MongoDBProxy==0.1.0
git+https://github.com/edx/nltk.git@2.0.6#egg=nltk==2.0.6
diff --git a/themes/red-theme/cms/templates/login.html b/themes/red-theme/cms/templates/login.html
deleted file mode 100644
index 70db0d13b3..0000000000
--- a/themes/red-theme/cms/templates/login.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<%inherit file="base.html" />
-<%def name="online_help_token()"><% return "login" %>%def>
-<%!
-from django.core.urlresolvers import reverse
-from django.utils.translation import ugettext as _
-%>
-<%block name="title">${_("Sign In")}%block>
-<%block name="bodyclass">not-signedin view-signin%block>
-
-<%block name="content">
-
-%block>
-
-<%block name="requirejs">
- require(["js/factories/login"], function(LoginFactory) {
- LoginFactory("${reverse('homepage')}");
- });
-%block>
diff --git a/themes/red-theme/lms/static/sass/_overrides.scss b/themes/red-theme/lms/static/sass/_overrides.scss
new file mode 100755
index 0000000000..4e5e1f2b6e
--- /dev/null
+++ b/themes/red-theme/lms/static/sass/_overrides.scss
@@ -0,0 +1,7 @@
+// Theming overrides for sample theme
+$header-bg: rgb(250,0,0);
+$footer-bg: rgb(250,0,0);
+$container-bg: rgb(250,0,0);
+$content-wrapper-bg: rgb(250,0,0);
+$serif: 'Comic Sans', 'Comic Sans MS';
+$sans-serif: 'Comic Sans', 'Comic Sans MS';
diff --git a/themes/red-theme/lms/static/sass/lms-main-rtl.scss b/themes/red-theme/lms/static/sass/lms-main-rtl.scss
new file mode 100755
index 0000000000..3eaad226a2
--- /dev/null
+++ b/themes/red-theme/lms/static/sass/lms-main-rtl.scss
@@ -0,0 +1,5 @@
+// Theming overrides for sample theme
+@import 'overrides';
+
+// import the rest of the application
+@import 'lms/static/sass/lms-main-rtl';
diff --git a/themes/red-theme/lms/static/sass/lms-main.scss b/themes/red-theme/lms/static/sass/lms-main.scss
new file mode 100755
index 0000000000..d6287e8215
--- /dev/null
+++ b/themes/red-theme/lms/static/sass/lms-main.scss
@@ -0,0 +1,5 @@
+// Theming overrides for sample theme
+@import 'overrides';
+
+// import the rest of the application
+@import 'lms/static/sass/lms-main';
diff --git a/themes/red-theme/lms/static/sass/partials/base/_variables.scss b/themes/red-theme/lms/static/sass/partials/base/_variables.scss
deleted file mode 100755
index c869ff9856..0000000000
--- a/themes/red-theme/lms/static/sass/partials/base/_variables.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-@import 'lms/static/sass/partials/base/variables';
-
-$header-bg: rgb(250,0,0);
-$footer-bg: rgb(250,0,0);
-$container-bg: rgb(250,0,0);