From 47255197e130e0c5aa17f815ffbb7a1ab6b0b4c9 Mon Sep 17 00:00:00 2001 From: Matt Tuchfarber <mtuchfarber@edx.org> Date: Fri, 6 Mar 2020 10:47:52 -0500 Subject: [PATCH] Allow plugins to update contexts in specific views Instead of requiring views like the dashboard to know about plugins so they can include their data in the context, this allows plugins to define a mapping between a view and a function where the function returns a dictionary of new context for the view. Each view would have to purposefully enable this additional context before it could be used. This will allow new content to be added to the pages without updating the core with a combination of a plugin to add new context, and a theme override of that page to use the new context. --- common/djangoapps/student/views/dashboard.py | 9 +++++ openedx/core/djangoapps/plugins/README.rst | 20 ++++++++++- openedx/core/djangoapps/plugins/constants.py | 16 +++++++++ .../djangoapps/plugins/plugin_contexts.py | 35 +++++++++++++++++++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 openedx/core/djangoapps/plugins/plugin_contexts.py diff --git a/common/djangoapps/student/views/dashboard.py b/common/djangoapps/student/views/dashboard.py index 38d4ef1fdad..96a57c69348 100644 --- a/common/djangoapps/student/views/dashboard.py +++ b/common/djangoapps/student/views/dashboard.py @@ -47,6 +47,8 @@ from openedx.core.djangoapps.catalog.utils import ( get_visible_sessions_for_entitlement ) from openedx.core.djangoapps.credit.email_utils import get_credit_provider_attribute_values, make_providers_strings +from openedx.core.djangoapps.plugins.plugin_contexts import get_plugins_view_context +from openedx.core.djangoapps.plugins import constants as plugin_constants from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.utils import ProgramDataExtender, ProgramProgressMeter from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers @@ -880,6 +882,13 @@ def student_dashboard(request): # TODO START: clean up as part of REVEM-199 (END) } + context_from_plugins = get_plugins_view_context( + plugin_constants.ProjectType.LMS, + plugin_constants.PluginContexts.VIEWS.STUDENT_DASHBOARD, + context + ) + context.update(context_from_plugins) + if ecommerce_service.is_enabled(request.user): context.update({ 'use_ecommerce_payment_flow': True, diff --git a/openedx/core/djangoapps/plugins/README.rst b/openedx/core/djangoapps/plugins/README.rst index 2013200231e..dfab6e0d267 100644 --- a/openedx/core/djangoapps/plugins/README.rst +++ b/openedx/core/djangoapps/plugins/README.rst @@ -106,7 +106,7 @@ class:: from django.apps import AppConfig from openedx.core.djangoapps.plugins.constants import ( - ProjectType, SettingsType, PluginURLs, PluginSettings + ProjectType, SettingsType, PluginURLs, PluginSettings, PluginContexts ) class MyAppConfig(AppConfig): name = u'full_python_path.my_app' @@ -184,6 +184,19 @@ class:: PluginSignals.SENDER_PATH: u'full_path_to_sender_app.ModelZ', }], } + }, + + # Configuration setting for Plugin Contexts for this app. + PluginContexts.CONFIG: { + + # Configure the Plugin Signals for each Project Type, as needed. + ProjectType.LMS: { + + # Key is the view that the app wishes to add context to and the value + # is the function within the app that will return additional context + # when called with the original context + PluginContexts.VIEWS.STUDENT_DASHBOARD: u'my_app.context_api.get_dashboard_context' + } } } @@ -217,6 +230,11 @@ OR use string constants when they cannot import from djangoapps.plugins:: u'sender_path': u'full_path_to_sender_app.ModelZ', }], } + }, + u'context_config': { + u'lms.djangoapp': { + 'student_dashboard': u'my_app.context_api.get_dashboard_context' + } } } diff --git a/openedx/core/djangoapps/plugins/constants.py b/openedx/core/djangoapps/plugins/constants.py index 56949535dff..8683429ce86 100644 --- a/openedx/core/djangoapps/plugins/constants.py +++ b/openedx/core/djangoapps/plugins/constants.py @@ -76,3 +76,19 @@ class PluginSignals(object): RELATIVE_PATH = u'relative_path' DEFAULT_RELATIVE_PATH = u'signals' + + +class PluginContextsViews(object): + STUDENT_DASHBOARD = u'student_dashboard' + + +class PluginContexts(object): + """ + The PluginContexts enum defines dictionary field names (and defaults) + that can be specified by a Plugin App in order to configure the + additional views it would like to add context into. + """ + CONFIG = u"context_config" + + VIEWS = PluginContextsViews + diff --git a/openedx/core/djangoapps/plugins/plugin_contexts.py b/openedx/core/djangoapps/plugins/plugin_contexts.py new file mode 100644 index 00000000000..abf482c67cf --- /dev/null +++ b/openedx/core/djangoapps/plugins/plugin_contexts.py @@ -0,0 +1,35 @@ +from importlib import import_module + +from logging import getLogger +from . import constants, registry + +log = getLogger(__name__) + + +def get_plugins_view_context(project_type, view_name, existing_context={}): + """ + Returns a dict of additonal view context. Will check if any plugin apps + have that view in their context_config, and if so will call their + selected function to get their context dicts. + """ + aggregate_context = {} + + for app_config in registry.get_app_configs(project_type): + context_function_path = _get_context_function(app_config, project_type, view_name) + if context_function_path: + module_path, _, name = context_function_path.rpartition('.') + context_function = getattr(import_module(module_path), name) + plugin_context = context_function(existing_context) + + # NOTE: If two plugins have try to set the same context keys, the last one + # called will overwrite the others. + aggregate_context.update(plugin_context) + + return aggregate_context + + +def _get_context_function(app_config, project_type, view_name): + plugin_config = getattr(app_config, constants.PLUGIN_APP_CLASS_ATTRIBUTE_NAME, {}) + context_config = plugin_config.get(constants.PluginContexts.CONFIG, {}) + project_type_settings = context_config.get(project_type, {}) + return project_type_settings.get(view_name) -- GitLab