Files
edx-platform/openedx/core/djangoapps/theming/helpers.py
John Eskew b866f35620 Remove support for COMPREHENSIVE_THEME_DIR -
all dirs must now go into COMPREHENSIVE_THEME_DIRS.
Move comprehensive theming setup section out of startup.py and into
  settings files using new 'derived' functionality.
Add 'derive_settings' at the end of all top-level Django settings files.
Move validation of comprehensive theming settings into new apps.py
  theming file.
Split theming code into code safe to run before settings are initialized
  -and- after settings are initialized.
2017-10-30 14:36:06 -04:00

339 lines
9.5 KiB
Python

"""
Helpers for accessing comprehensive theming related variables.
"""
import os
import re
from logging import getLogger
from django.conf import settings
from path import Path
from microsite_configuration import microsite
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.theming.helpers_dirs import (
get_theme_base_dirs_from_settings,
get_themes_unchecked,
get_theme_dirs,
get_project_root_name_from_settings,
Theme
)
from request_cache.middleware import RequestCache
logger = getLogger(__name__) # pylint: disable=invalid-name
def get_template_path(relative_path, **kwargs):
"""
This is a proxy function to hide microsite_configuration behind comprehensive theming.
"""
# 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)
return relative_path
def is_request_in_themed_site():
"""
This is a proxy function to hide microsite_configuration behind comprehensive theming.
"""
# 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):
"""
This is a proxy function to hide microsite_configuration behind comprehensive theming.
: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)
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.html')
'/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
"""
relative_path = os.path.normpath(relative_path)
theme = get_current_theme()
if not theme:
return relative_path
# strip `/` if present at the start of relative_path
template_name = re.sub(r'^/+', '', relative_path)
template_path = theme.template_path / template_name
absolute_path = theme.path / "templates" / template_name
if absolute_path.exists():
return str(template_path)
else:
return relative_path
def get_all_theme_template_dirs():
"""
Returns template directories for all the themes.
Example:
>> get_all_theme_template_dirs()
[
'/edx/app/edxapp/edx-platform/themes/red-theme/lms/templates/',
]
Returns:
(list): list of directories containing theme templates.
"""
themes = get_themes()
template_paths = list()
for theme in themes:
template_paths.extend(theme.template_dirs)
return template_paths
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
"""
return get_project_root_name_from_settings(settings.PROJECT_ROOT)
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.
"""
theme = get_current_theme()
if not theme:
return uri
templates_path = "/".join([
theme.theme_dir_name,
get_project_root_name(),
"templates"
])
uri = re.sub(r'^/*' + templates_path + '/*', '', uri)
return uri
def get_current_request():
"""
Return current request instance.
Returns:
(HttpRequest): returns current request
"""
return RequestCache.get_current_request()
def get_current_site():
"""
Return current site.
Returns:
(django.contrib.sites.models.Site): returns current site
"""
request = get_current_request()
if not request:
return None
return getattr(request, 'site', None)
def get_current_site_theme():
"""
Return current site theme object. Returns None if theming is disabled.
Returns:
(ecommerce.theming.models.SiteTheme): site theme object for the current site.
"""
# Return None if theming is disabled
if not is_comprehensive_theming_enabled():
return None
request = get_current_request()
if not request:
return None
return getattr(request, 'site_theme', None)
def get_current_theme():
"""
Return current theme object. Returns None if theming is disabled.
Returns:
(ecommerce.theming.models.SiteTheme): site theme object for the current site.
"""
# Return None if theming is disabled
if not is_comprehensive_theming_enabled():
return None
site_theme = get_current_site_theme()
if not site_theme:
return None
try:
return Theme(
name=site_theme.theme_dir_name,
theme_dir_name=site_theme.theme_dir_name,
themes_base_dir=get_theme_base_dir(site_theme.theme_dir_name),
project_root=get_project_root_name()
)
except ValueError as error:
# Log exception message and return None, so that open source theme is used instead
logger.exception('Theme not found in any of the themes dirs. [%s]', error)
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):
"""
Returns absolute path to the directory that contains the given theme.
Args:
theme_dir_name (str): theme directory name to get base path for
suppress_error (bool): if True function will return None if theme is not found instead of raising an error
Returns:
(str): Base directory that contains the given theme
"""
for themes_dir in get_theme_base_dirs():
if theme_dir_name in get_theme_dirs(themes_dir):
return themes_dir
if suppress_error:
return None
raise ValueError(
"Theme '{theme}' not found in any of the following themes dirs, \nTheme dirs: \n{dir}".format(
theme=theme_dir_name,
dir=get_theme_base_dirs(),
))
def theme_exists(theme_name, themes_dir=None):
"""
Returns True if a theme exists with the specified name.
"""
for theme in get_themes(themes_dir=themes_dir):
if theme.theme_dir_name == theme_name:
return True
return False
def get_themes(themes_dir=None):
"""
get a list of all themes known to the system.
Args:
themes_dir (str): (Optional) Path to themes base directory
Returns:
list of themes known to the system.
"""
if not is_comprehensive_theming_enabled():
return []
if themes_dir is None:
themes_dir = get_theme_base_dirs_unchecked()
return get_themes_unchecked(themes_dir, settings.PROJECT_ROOT)
def get_theme_base_dirs_unchecked():
"""
Return base directories that contains all the themes.
Example:
>> get_theme_base_dirs_unchecked()
['/edx/app/ecommerce/ecommerce/themes']
Returns:
(List of Paths): Base theme directory paths
"""
theme_dirs = getattr(settings, "COMPREHENSIVE_THEME_DIRS", None)
return get_theme_base_dirs_from_settings(theme_dirs)
def get_theme_base_dirs():
"""
Return base directories that contains all the themes.
Ensures comprehensive theming is enabled.
Example:
>> get_theme_base_dirs()
['/edx/app/ecommerce/ecommerce/themes']
Returns:
(List of Paths): Base theme directory paths
"""
# Return an empty list if theming is disabled
if not is_comprehensive_theming_enabled():
return []
return get_theme_base_dirs_unchecked()
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
"""
# 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
# Microsite configurations take priority over the default site theme.
if microsite.is_request_in_microsite():
return False
return settings.ENABLE_COMPREHENSIVE_THEMING