diff --git a/common/djangoapps/student/api.py b/common/djangoapps/student/api.py index f1c7d486f3..318f5a9143 100644 --- a/common/djangoapps/student/api.py +++ b/common/djangoapps/student/api.py @@ -44,6 +44,7 @@ MANUAL_ENROLLMENT_ROLE_CHOICES = configuration_helpers.get_value( COURSE_DASHBOARD_PLUGIN_VIEW_NAME = "course_dashboard" + def create_manual_enrollment_audit( enrolled_by, user_email, diff --git a/openedx/core/djangoapps/plugins/README.rst b/openedx/core/djangoapps/plugins/README.rst index cd0bd69337..c2b76d4be9 100644 --- a/openedx/core/djangoapps/plugins/README.rst +++ b/openedx/core/djangoapps/plugins/README.rst @@ -195,7 +195,7 @@ class:: # 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' + u'course_dashboard': u'my_app.context_api.get_dashboard_context' } } } @@ -233,7 +233,7 @@ OR use string constants when they cannot import from djangoapps.plugins:: }, u'view_context_config': { u'lms.djangoapp': { - 'student_dashboard': u'my_app.context_api.get_dashboard_context' + 'course_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 2ebb30eb71..a887162c3a 100644 --- a/openedx/core/djangoapps/plugins/constants.py +++ b/openedx/core/djangoapps/plugins/constants.py @@ -77,6 +77,7 @@ class PluginSignals(object): RELATIVE_PATH = u'relative_path' DEFAULT_RELATIVE_PATH = u'signals' + class PluginContexts(object): """ The PluginContexts enum defines dictionary field names (and defaults) diff --git a/docs/decisions/0003-plugin-contexts.rst b/openedx/core/djangoapps/plugins/docs/decisions/0003-plugin-contexts.rst similarity index 80% rename from docs/decisions/0003-plugin-contexts.rst rename to openedx/core/djangoapps/plugins/docs/decisions/0003-plugin-contexts.rst index 8f65dd2e26..7967f371b0 100644 --- a/docs/decisions/0003-plugin-contexts.rst +++ b/openedx/core/djangoapps/plugins/docs/decisions/0003-plugin-contexts.rst @@ -7,7 +7,7 @@ Draft Context ======= -edx-platform contains a plugin system which allows new Django apps to be installed inside the LMS and Studio without requiring the LMS/Studio to know about them. This is what enables us to move to a small and extensible core. While we had the ability to add settings, URLs, and signal handlers in our plugins, there wasn't any way for a plugin to affect the commonly used pages that the core was delivering. Thus a plugin couldn't change any details on the dashboard, courseware, or any other rendered page that the platform delivered. +edx-platform contains a plugin system (https://github.com/edx/edx-platform/tree/master/openedx/core/djangoapps/plugins) which allows new Django apps to be installed inside the LMS and Studio without requiring the LMS/Studio to know about them. This is what enables us to move to a small and extensible core. While we had the ability to add settings, URLs, and signal handlers in our plugins, there wasn't any way for a plugin to affect the commonly used pages that the core was delivering. Thus a plugin couldn't change any details on the dashboard, courseware, or any other rendered page that the platform delivered. Decisions ========= @@ -34,7 +34,7 @@ In the plugin app ~~~~~~~~~~~~~~~~~ Config ++++++ -Inside of your AppConfig your new plugin app, add a "view_context_config" item like below. +Inside of the AppConfig of your new plugin app, add a "view_context_config" item like below. * The format will be {"globally_unique_view_name": "function_inside_plugin_app"} * The function name & path don't need to be named anything specific, so long as they work * These functions will be called on **every** render of that view, so keep them efficient or memoize them if they aren't user specific. @@ -57,7 +57,7 @@ The function that will be called by the plugin system should accept a single par Example: :: - def my_context_function(existing_context): + def my_context_function(existing_context, *args, **kwargs): additional_context = {"some_plugin_value": 10} if existing_context.get("some_core_value"): additional_context.append({"some_other_plugin_value": True}) diff --git a/openedx/core/djangoapps/plugins/plugin_contexts.py b/openedx/core/djangoapps/plugins/plugin_contexts.py index 9f966ea880..8152ac96d4 100644 --- a/openedx/core/djangoapps/plugins/plugin_contexts.py +++ b/openedx/core/djangoapps/plugins/plugin_contexts.py @@ -9,19 +9,30 @@ from . import constants, registry log = getLogger(__name__) -def get_plugins_view_context(project_type, view_name, existing_context={}): + +def get_plugins_view_context(project_type, view_name, existing_context=None): """ - Returns a dict of additonal view context. Will check if any plugin apps + Returns a dict of additional view context. Will check if any plugin apps have that view in their view_context_config, and if so will call their selected function to get their context dicts. + + Params: + project_type: a string that determines which project (lms or studio) the view is being called in. See the + ProjectType enum in plugins/constants.py for valid options + view_name: a string that determines which view needs the additional context. These are globally unique and + noted in the api.py in the view's app. + existing_context: a dictionary which includes all of the data that the page was going to render with prior + to the addition of each plugin's context. This is what will be passed to plugins so they may choose + what data to add to the view. """ aggregate_context = {"plugins": {}} - # This functionality is cached - context_functions = _get_context_functions_for_view(project_type, view_name) + if existing_context is None: + existing_context = {} + + context_functions = _get_cached_context_functions_for_view(project_type, view_name) for (context_function, plugin_name) in context_functions: - plugin_context = context_function(existing_context) try: plugin_context = context_function(existing_context) except Exception as exc: @@ -37,14 +48,8 @@ def get_plugins_view_context(project_type, view_name, existing_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) - @process_cached -def _get_context_functions_for_view(project_type, view_name): +def _get_cached_context_functions_for_view(project_type, view_name): """ Returns a list of tuples where the first item is the context function and the second item is the name of the plugin it's being called from. @@ -55,7 +60,7 @@ def _get_context_functions_for_view(project_type, view_name): """ context_functions = [] for app_config in registry.get_app_configs(project_type): - context_function_path = _get_context_function(app_config, project_type, view_name) + context_function_path = _get_context_function_path(app_config, project_type, view_name) if context_function_path: module_path, _, name = context_function_path.rpartition('.') try: @@ -79,3 +84,10 @@ def _get_context_functions_for_view(project_type, view_name): view_name ) return context_functions + + +def _get_context_function_path(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)