Files
Régis Behmo 13342835b0 feat: theme-agnostic view to fetch theme assets (#29461)
It is possible to set custom logos in microfrontends, for instance with the
LOGO_URL setting. Ideally, we would like that MFEs share the same logos as the
LMS. But this is made difficult when comprehensive theming is enabled, and the
logo is overridden by a custom theme. In that scenario, the logo url becomes
/static/mytheme/images/logo.png. But the MFEs do no know that the "mytheme"
theme is enabled. To resolve this issue, we introduce here a view, at the
"/theming/asset/<path>" url, that redirects to the corresponding asset in the
theme that is currently enabled. Thus, MFEs only have to set
`LOGO_URL=http://lmshost/theming/asset/images/logo.png` to point to the themed
logo.

Related issue: https://github.com/overhangio/tutor-mfe/issues/25
2021-12-02 16:38:12 -05:00

154 lines
5.3 KiB
Python

"""
Views file for theming administration.
"""
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import Http404
from django.shortcuts import redirect
from django.template.loader import render_to_string
from django.utils.decorators import method_decorator
from django.utils.translation import gettext as _
from web_fragments.fragment import Fragment
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangoapps.user_api.preferences.api import (
delete_user_preference,
get_user_preference,
set_user_preference
)
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
from common.djangoapps.student.roles import GlobalStaff
from .helpers import theme_exists
from .helpers_static import get_static_file_url
from .models import SiteTheme
PREVIEW_SITE_THEME_PREFERENCE_KEY = 'preview-site-theme'
PREVIEW_THEME_FIELD = 'preview_theme'
def user_can_preview_themes(user):
"""
Returns true if the specified user is allowed to preview themes.
"""
if not user or user.is_anonymous:
return False
# In development mode, all users can preview themes
if settings.DEBUG:
return True
# Otherwise, only global staff can preview themes
return GlobalStaff().has_user(user)
def get_user_preview_site_theme(request):
"""
Returns the preview site for the current user, or None if not set.
"""
user = request.user
if not user or user.is_anonymous:
return None
preview_site_name = get_user_preference(user, PREVIEW_SITE_THEME_PREFERENCE_KEY)
if not preview_site_name:
return None
return SiteTheme(site=request.site, theme_dir_name=preview_site_name)
def set_user_preview_site_theme(request, preview_site_theme):
"""
Sets the current user's preferred preview site theme.
Args:
request: the current request
preview_site_theme (str or SiteTheme): the preview site theme or theme name.
None can be specified to remove the preview site theme.
"""
if preview_site_theme:
if isinstance(preview_site_theme, SiteTheme):
preview_site_theme_name = preview_site_theme.theme_dir_name
else:
preview_site_theme_name = preview_site_theme
if theme_exists(preview_site_theme_name):
set_user_preference(request.user, PREVIEW_SITE_THEME_PREFERENCE_KEY, preview_site_theme_name)
PageLevelMessages.register_success_message(
request,
_('Site theme changed to {site_theme}').format(site_theme=preview_site_theme_name)
)
else:
PageLevelMessages.register_error_message(
request,
_('Theme {site_theme} does not exist').format(site_theme=preview_site_theme_name)
)
else:
delete_user_preference(request.user, PREVIEW_SITE_THEME_PREFERENCE_KEY)
PageLevelMessages.register_success_message(request, _('Site theme reverted to the default'))
class ThemingAdministrationFragmentView(EdxFragmentView):
"""
Fragment view to allow a user to administer theming.
"""
def render_to_fragment(self, request, course_id=None, **kwargs): # lint-amnesty, pylint: disable=arguments-differ, unused-argument
"""
Renders the theming administration view as a fragment.
"""
html = render_to_string('theming/theming-admin-fragment.html', {})
return Fragment(html)
@method_decorator(login_required)
def get(self, request, *args, **kwargs):
"""
Renders the theming admin fragment to authorized users.
"""
if not user_can_preview_themes(request.user):
raise Http404
return super().get(request, *args, **kwargs)
@method_decorator(login_required)
def post(self, request, **kwargs): # lint-amnesty, pylint: disable=unused-argument
"""
Accept requests to update the theme preview.
"""
if not user_can_preview_themes(request.user):
raise Http404
action = request.POST.get('action', None)
if action == 'set_preview_theme':
preview_theme_name = request.POST.get(PREVIEW_THEME_FIELD, '')
set_user_preview_site_theme(request, preview_theme_name)
elif action == 'reset_preview_theme':
set_user_preview_site_theme(request, None)
return redirect(request.path)
def create_base_standalone_context(self, request, fragment, **kwargs):
"""
Creates the context to use when rendering a standalone page.
"""
return {
'uses_bootstrap': True,
}
def standalone_page_title(self, request, fragment, **kwargs):
"""
Returns the page title for the standalone update page.
"""
return _('Theming Administration')
def themed_asset(request, path):
"""
Redirect to themed asset.
This view makes it easy to link to theme assets without knowing what is the
currently enabled theme. For instance, applications outside of the LMS may
want to link to the LMS logo.
Note that the redirect is not permanent because the theme may change from
one run to the next.
"""
themed_url = get_static_file_url(path)
return redirect(themed_url, permanent=False)