315 lines
12 KiB
Python
315 lines
12 KiB
Python
"""Views for the branding app. """
|
|
|
|
import logging
|
|
|
|
import six
|
|
from django.conf import settings
|
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
|
from django.core.cache import cache
|
|
from django.db import transaction
|
|
from django.http import Http404, HttpResponse
|
|
from django.shortcuts import redirect
|
|
from django.urls.exceptions import NoReverseMatch
|
|
from django.utils import translation
|
|
from django.utils.translation.trans_real import get_supported_language_variant
|
|
from django.views.decorators.cache import cache_control
|
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
|
|
|
import lms.djangoapps.branding.api as branding_api
|
|
import lms.djangoapps.courseware.views.views as courseware_views
|
|
from common.djangoapps.edxmako.shortcuts import marketing_link, render_to_response
|
|
from common.djangoapps.student import views as student_views
|
|
from common.djangoapps.util.cache import cache_if_anonymous
|
|
from common.djangoapps.util.json_request import JsonResponse
|
|
from openedx.core.djangoapps.lang_pref.api import released_languages
|
|
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@ensure_csrf_cookie
|
|
@transaction.non_atomic_requests
|
|
@cache_if_anonymous()
|
|
def index(request):
|
|
"""
|
|
Redirects to main page -- info page if user authenticated, or marketing if not
|
|
"""
|
|
if request.user.is_authenticated:
|
|
# Only redirect to dashboard if user has
|
|
# courses in their dashboard. Otherwise UX is a bit cryptic.
|
|
# In this case, we want to have the user stay on a course catalog
|
|
# page to make it easier to browse for courses (and register)
|
|
if configuration_helpers.get_value(
|
|
'ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER',
|
|
settings.FEATURES.get('ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER', True)):
|
|
return redirect('dashboard')
|
|
|
|
enable_mktg_site = configuration_helpers.get_value(
|
|
'ENABLE_MKTG_SITE',
|
|
settings.FEATURES.get('ENABLE_MKTG_SITE', False)
|
|
)
|
|
|
|
if enable_mktg_site:
|
|
marketing_urls = configuration_helpers.get_value(
|
|
'MKTG_URLS',
|
|
settings.MKTG_URLS
|
|
)
|
|
return redirect(marketing_urls.get('ROOT'))
|
|
|
|
domain = request.META.get('HTTP_HOST')
|
|
|
|
# keep specialized logic for Edge until we can migrate over Edge to fully use
|
|
# configuration.
|
|
if domain and 'edge.edx.org' in domain:
|
|
return redirect("signin_user")
|
|
|
|
# we do not expect this case to be reached in cases where
|
|
# marketing and edge are enabled
|
|
|
|
try:
|
|
return student_views.index(request, user=request.user)
|
|
except NoReverseMatch:
|
|
log.error(
|
|
f'https is not a registered namespace Request from {domain}',
|
|
f'request_site= {request.site.__dict__}',
|
|
f'enable_mktg_site= {enable_mktg_site}',
|
|
f'Auth Status= {request.user.is_authenticated}',
|
|
f'Request Meta= {request.META}'
|
|
)
|
|
raise
|
|
|
|
|
|
@ensure_csrf_cookie
|
|
@cache_if_anonymous()
|
|
def courses(request):
|
|
"""
|
|
Render the "find courses" page. If the marketing site is enabled, redirect
|
|
to that. Otherwise, if subdomain branding is on, this is the university
|
|
profile page. Otherwise, it's the edX courseware.views.views.courses page
|
|
"""
|
|
enable_mktg_site = configuration_helpers.get_value(
|
|
'ENABLE_MKTG_SITE',
|
|
settings.FEATURES.get('ENABLE_MKTG_SITE', False)
|
|
)
|
|
|
|
if enable_mktg_site:
|
|
return redirect(marketing_link('COURSES'), permanent=True)
|
|
|
|
if not settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
|
|
raise Http404
|
|
|
|
# we do not expect this case to be reached in cases where
|
|
# marketing is enabled or the courses are not browsable
|
|
return courseware_views.courses(request)
|
|
|
|
|
|
def _footer_static_url(request, name):
|
|
"""Construct an absolute URL to a static asset. """
|
|
return request.build_absolute_uri(staticfiles_storage.url(name))
|
|
|
|
|
|
def _footer_css_urls(request, package_name):
|
|
"""Construct absolute URLs to CSS assets in a package. """
|
|
# We need this to work both in local development and in production.
|
|
# Unfortunately, in local development we don't run the full asset pipeline,
|
|
# so fully processed output files may not exist.
|
|
# For this reason, we use the *css package* name(s), rather than the static file name
|
|
# to identify the CSS file name(s) to include in the footer.
|
|
# We then construct an absolute URI so that external sites (such as the marketing site)
|
|
# can locate the assets.
|
|
package = settings.PIPELINE['STYLESHEETS'].get(package_name, {})
|
|
paths = [package['output_filename']] if not settings.DEBUG else package['source_filenames']
|
|
return [
|
|
_footer_static_url(request, path)
|
|
for path in paths
|
|
]
|
|
|
|
|
|
def _render_footer_html(request, show_openedx_logo, include_dependencies, include_language_selector, language):
|
|
"""Render the footer as HTML.
|
|
|
|
Arguments:
|
|
show_openedx_logo (bool): If True, include the OpenEdX logo in the rendered HTML.
|
|
include_dependencies (bool): If True, include JavaScript and CSS dependencies.
|
|
include_language_selector (bool): If True, include a language selector with all supported languages.
|
|
|
|
Returns: unicode
|
|
|
|
"""
|
|
bidi = 'rtl' if translation.get_language_bidi() else 'ltr'
|
|
css_name = settings.FOOTER_CSS['openedx'][bidi]
|
|
|
|
context = {
|
|
'hide_openedx_link': not show_openedx_logo,
|
|
'footer_js_url': _footer_static_url(request, 'js/footer-edx.js'),
|
|
'footer_css_urls': _footer_css_urls(request, css_name),
|
|
'bidi': bidi,
|
|
'include_dependencies': include_dependencies,
|
|
'include_language_selector': include_language_selector,
|
|
'language': language
|
|
}
|
|
|
|
return render_to_response("footer.html", context)
|
|
|
|
|
|
@cache_control(must_revalidate=True, max_age=settings.FOOTER_BROWSER_CACHE_MAX_AGE)
|
|
def footer(request):
|
|
"""Retrieve the branded footer.
|
|
|
|
This end-point provides information about the site footer,
|
|
allowing for consistent display of the footer across other sites
|
|
(for example, on the marketing site and blog).
|
|
|
|
It can be used in one of two ways:
|
|
1) A client renders the footer from a JSON description.
|
|
2) A browser loads an HTML representation of the footer
|
|
and injects it into the DOM. The HTML includes
|
|
CSS and JavaScript links.
|
|
|
|
In case (2), we assume that the following dependencies
|
|
are included on the page:
|
|
a) JQuery (same version as used in edx-platform)
|
|
b) font-awesome (same version as used in edx-platform)
|
|
c) Open Sans web fonts
|
|
|
|
Example: Retrieving the footer as JSON
|
|
|
|
GET /api/branding/v1/footer
|
|
Accepts: application/json
|
|
|
|
{
|
|
"navigation_links": [
|
|
{
|
|
"url": "http://example.com/about",
|
|
"name": "about",
|
|
"title": "About"
|
|
},
|
|
# ...
|
|
],
|
|
"social_links": [
|
|
{
|
|
"url": "http://example.com/social",
|
|
"name": "facebook",
|
|
"icon-class": "fa-facebook-square",
|
|
"title": "Facebook",
|
|
"action": "Sign up on Facebook!"
|
|
},
|
|
# ...
|
|
],
|
|
"mobile_links": [
|
|
{
|
|
"url": "http://example.com/android",
|
|
"name": "google",
|
|
"image": "http://example.com/google.png",
|
|
"title": "Google"
|
|
},
|
|
# ...
|
|
],
|
|
"legal_links": [
|
|
{
|
|
"url": "http://example.com/terms-of-service.html",
|
|
"name": "terms_of_service",
|
|
"title': "Terms of Service"
|
|
},
|
|
# ...
|
|
],
|
|
"openedx_link": {
|
|
"url": "https://open.edx.org",
|
|
"title": "Powered by Open edX",
|
|
"image": "http://example.com/openedx.png"
|
|
},
|
|
"logo_image": "http://example.com/static/images/logo.png",
|
|
"copyright": "edX, Open edX and their respective logos are registered trademarks of edX Inc."
|
|
}
|
|
|
|
|
|
Example: Retrieving the footer as HTML
|
|
|
|
GET /api/branding/v1/footer
|
|
Accepts: text/html
|
|
|
|
|
|
Example: Including the footer with the "Powered by Open edX" logo
|
|
|
|
GET /api/branding/v1/footer?show-openedx-logo=1
|
|
Accepts: text/html
|
|
|
|
|
|
Example: Retrieving the footer in a particular language
|
|
|
|
GET /api/branding/v1/footer?language=en
|
|
Accepts: text/html
|
|
|
|
|
|
Example: Retrieving the footer with a language selector
|
|
|
|
GET /api/branding/v1/footer?include-language-selector=1
|
|
Accepts: text/html
|
|
|
|
|
|
Example: Retrieving the footer with all JS and CSS dependencies (for testing)
|
|
|
|
GET /api/branding/v1/footer?include-dependencies=1
|
|
Accepts: text/html
|
|
|
|
"""
|
|
if not branding_api.is_enabled():
|
|
raise Http404
|
|
|
|
# Use the content type to decide what representation to serve
|
|
accepts = request.META.get('HTTP_ACCEPT', '*/*')
|
|
|
|
# Show the OpenEdX logo in the footer
|
|
show_openedx_logo = bool(request.GET.get('show-openedx-logo', False))
|
|
|
|
# Include JS and CSS dependencies
|
|
# This is useful for testing the end-point directly.
|
|
include_dependencies = bool(request.GET.get('include-dependencies', False))
|
|
|
|
# Override the language if necessary
|
|
language = request.GET.get('language', translation.get_language())
|
|
try:
|
|
language = get_supported_language_variant(language)
|
|
except LookupError:
|
|
language = settings.LANGUAGE_CODE
|
|
|
|
# Include a language selector
|
|
include_language_selector = request.GET.get('include-language-selector', '') == '1'
|
|
|
|
# Render the footer information based on the extension
|
|
if 'text/html' in accepts or '*/*' in accepts:
|
|
cache_params = {
|
|
'language': language,
|
|
'show_openedx_logo': show_openedx_logo,
|
|
'include_dependencies': include_dependencies
|
|
}
|
|
if include_language_selector:
|
|
cache_params['language_selector_options'] = ','.join(sorted([lang.code for lang in released_languages()]))
|
|
cache_key = "branding.footer.{params}.html".format(params=six.moves.urllib.parse.urlencode(cache_params))
|
|
|
|
content = cache.get(cache_key)
|
|
if content is None:
|
|
with translation.override(language):
|
|
content = _render_footer_html(
|
|
request, show_openedx_logo, include_dependencies, include_language_selector, language
|
|
)
|
|
cache.set(cache_key, content, settings.FOOTER_CACHE_TIMEOUT)
|
|
return HttpResponse(content, status=200, content_type="text/html; charset=utf-8")
|
|
|
|
elif 'application/json' in accepts:
|
|
cache_key = "branding.footer.{params}.json".format(
|
|
params=six.moves.urllib.parse.urlencode({
|
|
'language': language,
|
|
'is_secure': request.is_secure(),
|
|
})
|
|
)
|
|
footer_dict = cache.get(cache_key)
|
|
if footer_dict is None:
|
|
with translation.override(language):
|
|
footer_dict = branding_api.get_footer(is_secure=request.is_secure())
|
|
cache.set(cache_key, footer_dict, settings.FOOTER_CACHE_TIMEOUT)
|
|
return JsonResponse(footer_dict, 200, content_type="application/json; charset=utf-8") # lint-amnesty, pylint: disable=redundant-content-type-for-json-response
|
|
|
|
else:
|
|
return HttpResponse(status=406)
|