From f891d27cbb49ac6b8b5347ebdf4531133e06efa5 Mon Sep 17 00:00:00 2001 From: Jesse Zoldak Date: Wed, 16 Mar 2016 11:19:36 -0400 Subject: [PATCH] Revert "ziafazal/WL-328: Multi-Site Comprehensive Theming" This reverts commit 954dae584a059bc9236f6114414da62b34556c9d. --- cms/envs/aws.py | 1 - cms/envs/bok_choy.py | 4 +- cms/envs/common.py | 6 +- cms/envs/devstack.py | 1 - cms/envs/devstack_optimized.py | 4 +- cms/envs/test.py | 3 - cms/startup.py | 4 +- .../sass/{partials => }/_variables.scss | 0 .../course_modes/tests/test_views.py | 4 +- common/djangoapps/edxmako/paths.py | 31 +- common/djangoapps/edxmako/shortcuts.py | 13 +- .../microsite_configuration/backends/base.py | 4 +- .../tests/backends/test_database.py | 18 +- .../templates/static_content.html | 8 +- common/djangoapps/student/tests/test_email.py | 4 +- common/djangoapps/student/tests/tests.py | 2 + common/test/db_fixtures/sites.json | 20 - .../test/test-theme/cms/static/css/.gitignore | 1 - .../cms/static/sass/partials/_variables.scss | 255 ---------- .../test/test-theme/cms/templates/login.html | 55 -- .../test/test-theme/lms/static/css/.gitignore | 1 - .../test-theme/lms/static/images/logo.png | Bin 493 -> 0 bytes .../static/sass/partials/base/_variables.scss | 5 - .../test/test-theme/lms/templates/footer.html | 10 - lms/djangoapps/branding/tests/test_views.py | 60 +-- lms/djangoapps/commerce/tests/test_views.py | 5 +- .../tests/test_comprehensive_theming.py | 3 +- lms/djangoapps/course_wiki/views.py | 14 + .../tests/test_comprehensive_theming.py | 86 +--- .../courseware/tests/test_footer.py | 7 +- .../student_account/test/test_views.py | 16 +- .../verify_student/tests/test_views.py | 4 +- lms/envs/bok_choy.py | 4 +- lms/envs/common.py | 7 +- lms/envs/devstack.py | 1 - lms/envs/devstack_optimized.py | 4 +- lms/envs/test.py | 5 - lms/startup.py | 4 +- .../sass/{partials => }/base/_variables.scss | 0 lms/templates/main_django.html | 2 +- lms/templates/wiki/base.html | 2 +- lms/templates/wiki/preview_inline.html | 2 +- openedx/core/djangoapps/theming/admin.py | 22 - openedx/core/djangoapps/theming/core.py | 72 ++- openedx/core/djangoapps/theming/finders.py | 27 +- openedx/core/djangoapps/theming/helpers.py | 286 +---------- .../theming/migrations/0001_initial.py | 22 - .../djangoapps/theming/migrations/__init__.py | 0 openedx/core/djangoapps/theming/models.py | 19 - openedx/core/djangoapps/theming/storage.py | 96 ++-- .../djangoapps/theming/template_loaders.py | 32 -- .../theming/templatetags/theme_pipeline.py | 78 --- openedx/core/djangoapps/theming/test_util.py | 86 ++-- .../djangoapps/theming/tests/test_helpers.py | 130 ----- .../djangoapps/theming/tests/test_storage.py | 83 --- .../tests/test_theme_style_overrides.py | 235 --------- openedx/core/lib/tempdir.py | 19 - openedx/core/storage.py | 6 +- pavelib/assets.py | 471 ++++-------------- pavelib/paver_tests/test_assets.py | 154 +----- pavelib/paver_tests/test_servers.py | 1 + requirements/edx/github.txt | 2 +- themes/red-theme/cms/templates/login.html | 55 -- .../red-theme/lms/static/sass/_overrides.scss | 7 + .../lms/static/sass/lms-main-rtl.scss | 5 + .../red-theme/lms/static/sass/lms-main.scss | 5 + .../static/sass/partials/base/_variables.scss | 5 - 67 files changed, 411 insertions(+), 2187 deletions(-) rename cms/static/sass/{partials => }/_variables.scss (100%) delete mode 100644 common/test/db_fixtures/sites.json delete mode 100644 common/test/test-theme/cms/static/css/.gitignore delete mode 100644 common/test/test-theme/cms/static/sass/partials/_variables.scss delete mode 100644 common/test/test-theme/cms/templates/login.html delete mode 100644 common/test/test-theme/lms/static/css/.gitignore delete mode 100644 common/test/test-theme/lms/static/images/logo.png delete mode 100755 common/test/test-theme/lms/static/sass/partials/base/_variables.scss delete mode 100644 common/test/test-theme/lms/templates/footer.html rename lms/static/sass/{partials => }/base/_variables.scss (100%) delete mode 100644 openedx/core/djangoapps/theming/admin.py delete mode 100644 openedx/core/djangoapps/theming/migrations/0001_initial.py delete mode 100644 openedx/core/djangoapps/theming/migrations/__init__.py delete mode 100644 openedx/core/djangoapps/theming/models.py delete mode 100644 openedx/core/djangoapps/theming/template_loaders.py delete mode 100644 openedx/core/djangoapps/theming/templatetags/theme_pipeline.py delete mode 100644 openedx/core/djangoapps/theming/tests/test_helpers.py delete mode 100644 openedx/core/djangoapps/theming/tests/test_storage.py delete mode 100644 openedx/core/djangoapps/theming/tests/test_theme_style_overrides.py delete mode 100644 themes/red-theme/cms/templates/login.html create mode 100755 themes/red-theme/lms/static/sass/_overrides.scss create mode 100755 themes/red-theme/lms/static/sass/lms-main-rtl.scss create mode 100755 themes/red-theme/lms/static/sass/lms-main.scss delete mode 100755 themes/red-theme/lms/static/sass/partials/base/_variables.scss 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" %> -<%! -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext as _ -%> -<%block name="title">${_("Sign In")} -<%block name="bodyclass">not-signedin view-signin - -<%block name="content"> -
-
-
-

${_("Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)}

- -
- -
-
- -
- ${_("Required Information to Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)} - - -
    -
  1. - - -
  2. - -
  3. - - - ${_("Forgot password?")} -
  4. -
-
- -
- -
- - - -
-
-
-
- - -<%block name="requirejs"> - require(["js/factories/login"], function(LoginFactory) { - LoginFactory("${reverse('homepage')}"); - }); - 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 5efc6b63a436b749addf0c7a978f9cb78ba03eab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 493 zcmVya;Rt@{2;1;)O>oj)&R5}5gerKjf&*Ma zhww8Gs!&WbOdVDrF|DAULKqiDATf=={bZO5Y(Ns)K;;8r9GHP5Gy^AT91rBfyaaI? zARpFvH_jX+CG$7p^gvS5iyLPNlCoutI75(>4PT8j0m;bZg*aW1jC6mC(+0^{+Y+Y+ zlChd8jti0#_eq=*NKQ)60xfb3w0II|kYk|1W1KQb&dLwZI+8Od8fdH_&^QvPqaaWx z8tAGZ&@~dsR}jdL?y*FPdbr0DCEOrTUs0ew7S&P|s1*cSD+;v6qNa)hO@lytMS=EM z)J{>LT@dK3D9{;;Iw=Zt3Ig2~1-fHVH${PN{07;b_#0%m<9{)`DW8MvT6~hWbGa8Y z^SPV1>+pS$ox?YZW&^&Hwkz>e!>q=0F|#gDR?UVyn>Xw6c86Jm_k%3=gLuQtaKnsu j6is&&IUEj$^LOS4BtH-m2H0bh00000NkvXXu0mjf=WNR> 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("
TEMPORARY THEME
") - 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" %> -<%! -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext as _ -%> -<%block name="title">${_("Sign In")} -<%block name="bodyclass">not-signedin view-signin - -<%block name="content"> -
-
-
-

${_("Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)}

- -
- -
-
- -
- ${_("Required Information to Sign In to {studio_name}").format(studio_name=settings.STUDIO_NAME)} - - -
    -
  1. - - -
  2. - -
  3. - - - ${_("Forgot password?")} -
  4. -
-
- -
- -
- - - -
-
-
-
- - -<%block name="requirejs"> - require(["js/factories/login"], function(LoginFactory) { - LoginFactory("${reverse('homepage')}"); - }); - 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);