Commit f974347d by Saleem Latif

Update theming to have priority over microsites.

parent 33ae93ec
...@@ -11,7 +11,7 @@ from django.contrib.staticfiles.storage import staticfiles_storage ...@@ -11,7 +11,7 @@ from django.contrib.staticfiles.storage import staticfiles_storage
from request_cache.middleware import RequestCache from request_cache.middleware import RequestCache
from microsite_configuration import microsite from microsite_configuration import microsite
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from logging import getLogger from logging import getLogger
logger = getLogger(__name__) # pylint: disable=invalid-name logger = getLogger(__name__) # pylint: disable=invalid-name
...@@ -21,7 +21,10 @@ def get_template_path(relative_path, **kwargs): ...@@ -21,7 +21,10 @@ def get_template_path(relative_path, **kwargs):
""" """
This is a proxy function to hide microsite_configuration behind comprehensive theming. This is a proxy function to hide microsite_configuration behind comprehensive theming.
""" """
if microsite.is_request_in_microsite(): # We need to give priority to theming over microsites
# So, we apply microsite override only if there is no associated site theme
# and associated microsite is present.
if not current_request_has_associated_site_theme() and microsite.is_request_in_microsite():
relative_path = microsite.get_template_path(relative_path, **kwargs) relative_path = microsite.get_template_path(relative_path, **kwargs)
return relative_path return relative_path
...@@ -30,7 +33,8 @@ def is_request_in_themed_site(): ...@@ -30,7 +33,8 @@ def is_request_in_themed_site():
""" """
This is a proxy function to hide microsite_configuration behind comprehensive theming. This is a proxy function to hide microsite_configuration behind comprehensive theming.
""" """
return microsite.is_request_in_microsite() # We need to give priority to theming/site-configuration over microsites
return configuration_helpers.is_site_configuration_enabled() or microsite.is_request_in_microsite()
def get_template(uri): def get_template(uri):
...@@ -38,6 +42,9 @@ def get_template(uri): ...@@ -38,6 +42,9 @@ def get_template(uri):
This is a proxy function to hide microsite_configuration behind comprehensive theming. This is a proxy function to hide microsite_configuration behind comprehensive theming.
:param uri: uri of the template :param uri: uri of the template
""" """
# We need to give priority to theming over microsites
# So, we apply microsite template override only when there is no associated theme,
if not current_request_has_associated_site_theme():
return microsite.get_template(uri) return microsite.get_template(uri)
...@@ -190,6 +197,18 @@ def get_current_theme(): ...@@ -190,6 +197,18 @@ def get_current_theme():
return None return None
def current_request_has_associated_site_theme():
"""
True if current request has an associated SiteTheme, False otherwise.
Returns:
True if current request has an associated SiteTheme, False otherwise
"""
request = get_current_request()
site_theme = getattr(request, 'site_theme', None)
return bool(site_theme and site_theme.id)
def get_theme_base_dir(theme_dir_name, suppress_error=False): def get_theme_base_dir(theme_dir_name, suppress_error=False):
""" """
Returns absolute path to the directory that contains the given theme. Returns absolute path to the directory that contains the given theme.
...@@ -298,7 +317,12 @@ def is_comprehensive_theming_enabled(): ...@@ -298,7 +317,12 @@ def is_comprehensive_theming_enabled():
Returns: Returns:
(bool): True if comprehensive theming is enabled else False (bool): True if comprehensive theming is enabled else False
""" """
# We need to give priority to theming over microsites
if settings.ENABLE_COMPREHENSIVE_THEMING and current_request_has_associated_site_theme():
return True
# Disable theming for microsites # Disable theming for microsites
# Microsite configurations take priority over the default site theme.
if microsite.is_request_in_microsite(): if microsite.is_request_in_microsite():
return False return False
......
...@@ -5,7 +5,7 @@ Note: ...@@ -5,7 +5,7 @@ Note:
This middleware depends on "django_sites_extensions" app This middleware depends on "django_sites_extensions" app
So it must be added to INSTALLED_APPS in django settings files. So it must be added to INSTALLED_APPS in django settings files.
""" """
from django.conf import settings
from openedx.core.djangoapps.theming.models import SiteTheme from openedx.core.djangoapps.theming.models import SiteTheme
...@@ -18,4 +18,5 @@ class CurrentSiteThemeMiddleware(object): ...@@ -18,4 +18,5 @@ class CurrentSiteThemeMiddleware(object):
""" """
fetch Site Theme for the current site and add it to the request object. fetch Site Theme for the current site and add it to the request object.
""" """
request.site_theme = SiteTheme.get_theme(request.site) default_theme = SiteTheme(site=request.site, theme_dir_name=settings.DEFAULT_SITE_THEME)
request.site_theme = SiteTheme.get_theme(request.site, default=default_theme)
...@@ -20,20 +20,33 @@ class SiteTheme(models.Model): ...@@ -20,20 +20,33 @@ class SiteTheme(models.Model):
return self.theme_dir_name return self.theme_dir_name
@staticmethod @staticmethod
def get_theme(site): def get_theme(site, default=None):
""" """
Get SiteTheme object for given site, returns default site theme if it can not Get SiteTheme object for given site, returns default site theme if it can not
find a theme for the given site and `DEFAULT_SITE_THEME` setting has a proper value. find a theme for the given site and `DEFAULT_SITE_THEME` setting has a proper value.
Args: Args:
site (django.contrib.sites.models.Site): site object related to the current site. site (django.contrib.sites.models.Site): site object related to the current site.
default (openedx.core.djangoapps.models.SiteTheme): site theme object to return in case there is no theme
associated for the given site.
Returns: Returns:
SiteTheme object for given site or a default site set by `DEFAULT_SITE_THEME` SiteTheme object for given site or a default site passed in as the argument.
""" """
theme = site.themes.first() theme = site.themes.first()
return theme or default
if (not theme) and settings.DEFAULT_SITE_THEME: @staticmethod
theme = SiteTheme(site=site, theme_dir_name=settings.DEFAULT_SITE_THEME) def has_theme(site):
return theme """
Returns True if given site has an associated site theme in database, returns False otherwise.
Note: DEFAULT_SITE_THEME is not considered as an associated site.
Args:
site (django.contrib.sites.models.Site): site object related to the current site.
Returns:
True if given site has an associated site theme in database, returns False otherwise.
"""
return site.themes.exists()
...@@ -2,13 +2,14 @@ ...@@ -2,13 +2,14 @@
Test helpers for Comprehensive Theming. Test helpers for Comprehensive Theming.
""" """
import unittest import unittest
from mock import patch from mock import patch, Mock
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
from django.conf import settings from django.conf import settings
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.theming import helpers as theming_helpers
from openedx.core.djangoapps.theming.helpers import get_template_path_with_theme, strip_site_theme_templates_path, \ from openedx.core.djangoapps.theming.helpers import get_template_path_with_theme, strip_site_theme_templates_path, \
get_themes, Theme, get_theme_base_dir get_themes, Theme, get_theme_base_dir
...@@ -54,6 +55,152 @@ class TestHelpers(TestCase): ...@@ -54,6 +55,152 @@ class TestHelpers(TestCase):
jwt_auth = configuration_helpers.get_value('JWT_AUTH') jwt_auth = configuration_helpers.get_value('JWT_AUTH')
self.assertEqual(jwt_auth[override_key], override_value) self.assertEqual(jwt_auth[override_key], override_value)
def test_is_comprehensive_theming_enabled(self):
"""
Tests to make sure the is_comprehensive_theming_enabled function works as expected.
Here are different scenarios that we need to test
1. Theming is enabled, there is a SiteTheme record and microsite configuration for the current site.
is_comprehensive_theming_enabled should return True
2. Theming is enabled, there is no SiteTheme record but there is microsite configuration for the current site.
is_comprehensive_theming_enabled should return False
3. Theming is enabled, there is neither a SiteTheme record nor microsite configuration for the current site.
is_comprehensive_theming_enabled should return True
4. Theming is disabled, there is a SiteTheme record and microsite configuration for the current site.
is_comprehensive_theming_enabled should return False
5. Theming is disabled, there is no SiteTheme record but there is microsite configuration for the current site.
is_comprehensive_theming_enabled should return False
6. Theming is disabled, there is neither a SiteTheme record nor microsite configuration for the current site.
is_comprehensive_theming_enabled should return False
"""
# Theming is enabled, there is a SiteTheme record and microsite configuration for the current site
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=True),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=True),
):
self.assertTrue(theming_helpers.is_comprehensive_theming_enabled())
# Theming is enabled, there is no SiteTheme record but there is microsite configuration for the current site.
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=False),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=True),
):
self.assertFalse(theming_helpers.is_comprehensive_theming_enabled())
# Theming is enabled, there is neither a SiteTheme record nor microsite configuration for the current site.
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=False),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=False),
):
self.assertTrue(theming_helpers.is_comprehensive_theming_enabled())
with override_settings(ENABLE_COMPREHENSIVE_THEMING=False):
# Theming is disabled, there is a SiteTheme record and microsite configuration for the current site.
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=True),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=True),
):
self.assertFalse(theming_helpers.is_comprehensive_theming_enabled())
# Theming is disabled, there is no SiteTheme record but
# there is microsite configuration for the current site.
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=False),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=True),
):
self.assertFalse(theming_helpers.is_comprehensive_theming_enabled())
# Theming is disabled, there is neither a SiteTheme record nor microsite configuration for the current site.
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=False),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=False),
):
self.assertFalse(theming_helpers.is_comprehensive_theming_enabled())
def test_get_template(self):
"""
Tests to make sure the get_template function works as expected.
"""
# if the current site has associated SiteTheme then get_template should return None
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=True),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=True),
):
with patch("microsite_configuration.microsite.TEMPLATES_BACKEND") as mock_microsite_backend:
mock_microsite_backend.get_template = Mock(return_value="/microsite/about.html")
self.assertIsNone(theming_helpers.get_template("about.html"))
# if the current site does not have associated SiteTheme then get_template should return microsite override
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=False),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=True),
):
with patch("microsite_configuration.microsite.TEMPLATES_BACKEND") as mock_microsite_backend:
mock_microsite_backend.get_template = Mock(return_value="/microsite/about.html")
self.assertEqual(theming_helpers.get_template("about.html"), "/microsite/about.html")
def test_get_template_path(self):
"""
Tests to make sure the get_template_path function works as expected.
"""
# if the current site has associated SiteTheme then get_template_path should return the argument as is.
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=True),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=True),
):
with patch("microsite_configuration.microsite.TEMPLATES_BACKEND") as mock_microsite_backend:
mock_microsite_backend.get_template = Mock(return_value="/microsite/about.html")
self.assertEqual(theming_helpers.get_template_path("about.html"), "about.html")
# if the current site does not have associated SiteTheme then get_template_path should return microsite override
with patch(
"openedx.core.djangoapps.theming.helpers.current_request_has_associated_site_theme",
Mock(return_value=False),
):
with patch(
"openedx.core.djangoapps.theming.helpers.microsite.is_request_in_microsite",
Mock(return_value=True),
):
with patch("microsite_configuration.microsite.TEMPLATES_BACKEND") as mock_microsite_backend:
mock_microsite_backend.get_template_path = Mock(return_value="/microsite/about.html")
self.assertEqual(theming_helpers.get_template_path("about.html"), "/microsite/about.html")
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TestHelpersLMS(TestCase): class TestHelpersLMS(TestCase):
......
"""
Tests for microsites and comprehensive themes.
"""
import unittest
from django.conf import settings
from django.test import TestCase
from django.contrib.sites.models import Site
from openedx.core.djangoapps.theming.models import SiteTheme
@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 __add_site_theme__(self, domain, theme):
"""
Add a Site and SiteTheme record for the given domain and theme
Args:
domain: domain to which attach the new Site
theme: theme to apply on the new site
"""
site, __ = Site.objects.get_or_create(domain=domain, name=domain)
SiteTheme.objects.get_or_create(site=site, theme_dir_name=theme)
def test_theme_footer(self):
"""
Test that theme footer is used instead of microsite footer.
"""
# Add SiteTheme with the same domain name as microsite
self.__add_site_theme__(domain=settings.MICROSITE_TEST_HOSTNAME, theme="test-theme")
# Test that requesting on a host, where both theme and microsite is applied
# theme gets priority over microsite.
resp = self.client.get('/', HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
self.assertEqual(resp.status_code, 200)
# This string comes from footer.html of test-theme
self.assertContains(resp, "This is a footer for test-theme.")
def test_microsite_footer(self):
"""
Test that microsite footer is used instead of default theme footer.
"""
# Test that if theming is enabled but there is no SiteTheme for the current site, then
# DEFAULT_SITE_THEME does not interfere with microsites
resp = self.client.get('/', HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
self.assertEqual(resp.status_code, 200)
# This string comes from footer.html of test_site, which is a microsite
self.assertContains(resp, "This is a Test Site footer")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment