ol-openedx-feedback 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ol_openedx_feedback/__init__.py +0 -0
- ol_openedx_feedback/apps.py +27 -0
- ol_openedx_feedback/block.py +93 -0
- ol_openedx_feedback/compat.py +20 -0
- ol_openedx_feedback/constants.py +7 -0
- ol_openedx_feedback/settings/common.py +18 -0
- ol_openedx_feedback/static/css/feedback.css +88 -0
- ol_openedx_feedback/static/html/student_view.html +8 -0
- ol_openedx_feedback/static/js/feedback.js +77 -0
- ol_openedx_feedback/utils.py +26 -0
- ol_openedx_feedback-0.1.0.dist-info/METADATA +85 -0
- ol_openedx_feedback-0.1.0.dist-info/RECORD +14 -0
- ol_openedx_feedback-0.1.0.dist-info/WHEEL +4 -0
- ol_openedx_feedback-0.1.0.dist-info/entry_points.txt +5 -0
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""ol_openedx_feedback Django application initialization."""
|
|
2
|
+
|
|
3
|
+
from django.apps import AppConfig
|
|
4
|
+
from edx_django_utils.plugins import PluginSettings
|
|
5
|
+
from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OLOpenedxFeedbackConfig(AppConfig):
|
|
9
|
+
"""Configuration for the ol_openedx_feedback Django application.
|
|
10
|
+
|
|
11
|
+
Trigger-only plugin: the feedback trigger is registered via the
|
|
12
|
+
``xblock_asides.v1`` entry point (no URLs or models — feedback is persisted
|
|
13
|
+
in mit-learn and the MFE owns the submit URL). LMS-only, since the trigger
|
|
14
|
+
renders in the learner (student) view; the only Django settings are the
|
|
15
|
+
plugin defaults populated by ``settings.common``.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
name = "ol_openedx_feedback"
|
|
19
|
+
verbose_name = "Open edX Block Feedback"
|
|
20
|
+
|
|
21
|
+
plugin_app = {
|
|
22
|
+
PluginSettings.CONFIG: {
|
|
23
|
+
ProjectType.LMS: {
|
|
24
|
+
SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: "settings.common"},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""XBlockAside that renders the per-block feedback trigger."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import pkg_resources
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.template import Context, Template
|
|
8
|
+
from django.utils.translation import gettext
|
|
9
|
+
from web_fragments.fragment import Fragment
|
|
10
|
+
from xblock.core import XBlockAside
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from xmodule.modulestore.xml import (
|
|
14
|
+
XMLImportingModuleStoreRuntime as XMLImportingModuleStoreRuntime, # noqa: PLC0414
|
|
15
|
+
)
|
|
16
|
+
except ImportError:
|
|
17
|
+
from xmodule.modulestore.xml import ImportSystem as XMLImportingModuleStoreRuntime
|
|
18
|
+
|
|
19
|
+
from xmodule.x_module import STUDENT_VIEW
|
|
20
|
+
|
|
21
|
+
from ol_openedx_feedback.compat import get_feedback_enabled_flag
|
|
22
|
+
from ol_openedx_feedback.utils import is_aside_applicable_to_block
|
|
23
|
+
|
|
24
|
+
log = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _resource(path):
|
|
28
|
+
"""Return the decoded contents of a packaged resource."""
|
|
29
|
+
return pkg_resources.resource_string(__name__, path).decode("utf-8")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _render(path, context):
|
|
33
|
+
"""Render a packaged Django template with the given context."""
|
|
34
|
+
return Template(_resource(path)).render(Context(context))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FeedbackAside(XBlockAside):
|
|
38
|
+
"""Adds a small 'Send feedback' trigger to applicable blocks in the LMS."""
|
|
39
|
+
|
|
40
|
+
@XBlockAside.aside_for(STUDENT_VIEW)
|
|
41
|
+
def student_view_aside(self, block, context=None): # noqa: ARG002
|
|
42
|
+
"""Render the feedback trigger for authenticated learners only."""
|
|
43
|
+
fragment = Fragment("")
|
|
44
|
+
|
|
45
|
+
# Never render in Studio author/preview or for anonymous/preview users.
|
|
46
|
+
if getattr(self.runtime, "is_author_mode", False) or not getattr(
|
|
47
|
+
self.runtime, "user_id", None
|
|
48
|
+
):
|
|
49
|
+
return fragment
|
|
50
|
+
|
|
51
|
+
block_usage_key = block.usage_key
|
|
52
|
+
block_id = block_usage_key.block_id
|
|
53
|
+
block_type = getattr(block, "category", None)
|
|
54
|
+
|
|
55
|
+
fragment.add_content(
|
|
56
|
+
_render(
|
|
57
|
+
"static/html/student_view.html",
|
|
58
|
+
{
|
|
59
|
+
"block_id": block_id,
|
|
60
|
+
"label": gettext("Send feedback"),
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
fragment.add_css(_resource("static/css/feedback.css"))
|
|
65
|
+
fragment.add_javascript(_resource("static/js/feedback.js"))
|
|
66
|
+
fragment.initialize_js(
|
|
67
|
+
"FeedbackAsideInit",
|
|
68
|
+
json_args={
|
|
69
|
+
"block_id": block_id,
|
|
70
|
+
"learning_mfe_base_url": getattr(
|
|
71
|
+
settings, "LEARNING_MICROFRONTEND_URL", ""
|
|
72
|
+
),
|
|
73
|
+
"drawer_payload": {
|
|
74
|
+
"courseId": str(block_usage_key.course_key),
|
|
75
|
+
"blockUsageKey": str(block_usage_key),
|
|
76
|
+
"blockType": block_type,
|
|
77
|
+
"blockDisplayName": block.display_name or "",
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
return fragment
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def should_apply_to_block(cls, block):
|
|
85
|
+
"""Apply to leaf blocks when the course waffle flag is enabled."""
|
|
86
|
+
# During course import the block lacks a resolvable course context, so
|
|
87
|
+
# skip the waffle-flag check (which reads the course context key) and
|
|
88
|
+
# gate on block type only.
|
|
89
|
+
if isinstance(block.runtime, XMLImportingModuleStoreRuntime):
|
|
90
|
+
return is_aside_applicable_to_block(block)
|
|
91
|
+
return get_feedback_enabled_flag().is_enabled(
|
|
92
|
+
block.scope_ids.usage_id.context_key
|
|
93
|
+
) and is_aside_applicable_to_block(block)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Compatibility layer isolating core-platform imports."""
|
|
2
|
+
|
|
3
|
+
WAFFLE_FLAG_NAMESPACE = "ol_openedx_feedback"
|
|
4
|
+
|
|
5
|
+
# .. toggle_name: ol_openedx_feedback.feedback_enabled
|
|
6
|
+
# .. toggle_implementation: CourseWaffleFlag
|
|
7
|
+
# .. toggle_default: False
|
|
8
|
+
# .. toggle_description: Enables the per-block feedback trigger for a course.
|
|
9
|
+
# .. toggle_use_cases: open_edx
|
|
10
|
+
# .. toggle_creation_date: 2026-06-09
|
|
11
|
+
OL_OPENEDX_FEEDBACK_ENABLED = "feedback_enabled"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_feedback_enabled_flag():
|
|
15
|
+
"""Return the CourseWaffleFlag controlling feedback rollout."""
|
|
16
|
+
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag # noqa: PLC0415
|
|
17
|
+
|
|
18
|
+
return CourseWaffleFlag(
|
|
19
|
+
f"{WAFFLE_FLAG_NAMESPACE}.{OL_OPENEDX_FEEDBACK_ENABLED}", __name__
|
|
20
|
+
)
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Constants for ol_openedx_feedback."""
|
|
2
|
+
|
|
3
|
+
# Default structural/container blocks that never get a feedback trigger.
|
|
4
|
+
# Deployments can override this via the
|
|
5
|
+
# ``OL_OPENEDX_FEEDBACK_EXCLUDED_BLOCK_TYPES`` Django setting (e.g. to also
|
|
6
|
+
# exclude a content type like ``html``).
|
|
7
|
+
DEFAULT_EXCLUDED_BLOCK_TYPES = {"course", "chapter", "sequential", "vertical"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# noqa: INP001
|
|
2
|
+
|
|
3
|
+
"""Settings to provide to edX"""
|
|
4
|
+
|
|
5
|
+
from ol_openedx_feedback.constants import DEFAULT_EXCLUDED_BLOCK_TYPES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def plugin_settings(settings):
|
|
9
|
+
"""
|
|
10
|
+
Populate common settings
|
|
11
|
+
"""
|
|
12
|
+
env_tokens = getattr(settings, "ENV_TOKENS", {})
|
|
13
|
+
settings.OL_OPENEDX_FEEDBACK_EXCLUDED_BLOCK_TYPES = set(
|
|
14
|
+
env_tokens.get(
|
|
15
|
+
"OL_OPENEDX_FEEDBACK_EXCLUDED_BLOCK_TYPES",
|
|
16
|
+
DEFAULT_EXCLUDED_BLOCK_TYPES,
|
|
17
|
+
)
|
|
18
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
.ol-feedback-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
justify-content: flex-end;
|
|
4
|
+
margin: 8px 0 4px;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.ol-feedback-container--relocated {
|
|
8
|
+
margin: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.ol-feedback-anchor {
|
|
12
|
+
--ol-fb-brand: #8a1e2d;
|
|
13
|
+
position: relative;
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-self: center;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ol-feedback-anchor--docked {
|
|
19
|
+
/* Docked next to AskTIM: only positioning here (a gap before the AskTIM
|
|
20
|
+
button). The button appearance lives on .ol-feedback-trigger so the
|
|
21
|
+
megaphone always looks like a button, docked or standalone. */
|
|
22
|
+
margin-right: 10px;
|
|
23
|
+
z-index: 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* Problem blocks draw a horizontal "saved/submit" notification line behind the
|
|
27
|
+
button row. Project an opaque white block across the gap to the AskTIM button
|
|
28
|
+
so the line is masked there too (invisible on the white problem-block
|
|
29
|
+
background). Not applied to video/html blocks, which have no such line.
|
|
30
|
+
Offset must match the docked gap (margin-right above) so the mask fills the
|
|
31
|
+
gap but stops at the AskTIM button's edge instead of overlapping its border. */
|
|
32
|
+
.ol-feedback-anchor--line-masked {
|
|
33
|
+
box-shadow: 10px 0 0 0 #fff;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.ol-feedback-trigger {
|
|
37
|
+
/* Always styled as a button (border + background), whether standalone or
|
|
38
|
+
docked next to the AskTIM button. */
|
|
39
|
+
display: inline-flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
width: 32px;
|
|
43
|
+
height: 32px;
|
|
44
|
+
padding: 0;
|
|
45
|
+
border: 1px solid #b8c2cc;
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
background: #fff;
|
|
48
|
+
color: #e0a106;
|
|
49
|
+
cursor: pointer;
|
|
50
|
+
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.ol-feedback-trigger:hover {
|
|
54
|
+
background: #fbf3dd;
|
|
55
|
+
color: #c98a00;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* On click/focus the background fills the whole button and the border turns
|
|
59
|
+
red. */
|
|
60
|
+
.ol-feedback-trigger:active,
|
|
61
|
+
.ol-feedback-trigger:focus {
|
|
62
|
+
background: #e6e6e6;
|
|
63
|
+
border-color: var(--ol-fb-brand);
|
|
64
|
+
color: #c98a00;
|
|
65
|
+
outline: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ol-feedback-trigger .ol-fb-ic {
|
|
69
|
+
width: 18px;
|
|
70
|
+
height: 18px;
|
|
71
|
+
display: block;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* On video blocks the AskTIM chat container (.video-block-chat-button-container)
|
|
75
|
+
paints an opaque whitesmoke background plus an upward box-shadow inside its own
|
|
76
|
+
stacking context, which rides up over the platform's preceding "Staff Debug
|
|
77
|
+
Info" link (.wrap-instructor-info) and hides it for staff. Fix it here in the
|
|
78
|
+
feedback plugin so it holds regardless of the installed chat-plugin version.
|
|
79
|
+
The staff-info row is the adjacent sibling of the block's wrapper
|
|
80
|
+
(data-block-type="video"), so scope to video only: give the row its own
|
|
81
|
+
stacking context above the paint, and match the chat container's whitesmoke
|
|
82
|
+
background so it blends into the section instead of showing a white patch.
|
|
83
|
+
Problem/HTML rows are untouched. */
|
|
84
|
+
[data-block-type="video"] + .wrap-instructor-info {
|
|
85
|
+
position: relative;
|
|
86
|
+
z-index: 2;
|
|
87
|
+
background: whitesmoke;
|
|
88
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<div class="ol-feedback-container">
|
|
2
|
+
<span class="ol-feedback-anchor">
|
|
3
|
+
<button class="ol-feedback-trigger" id="ol-feedback-trigger-{{ block_id }}" type="button"
|
|
4
|
+
aria-haspopup="dialog" aria-label="{{ label }}" title="{{ label }}">
|
|
5
|
+
<svg class="ol-fb-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m3 11 18-5v12L3 14v-3z"/><path d="M11.6 16.8a3 3 0 1 1-5.8-1.6"/></svg>
|
|
6
|
+
</button>
|
|
7
|
+
</span>
|
|
8
|
+
</div>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
(function ($) {
|
|
2
|
+
function initFeedback(initArgs) {
|
|
3
|
+
var blockId = initArgs.block_id;
|
|
4
|
+
var mfeBaseUrl = initArgs.learning_mfe_base_url;
|
|
5
|
+
var payload = initArgs.drawer_payload || {};
|
|
6
|
+
|
|
7
|
+
var $trigger = $("#ol-feedback-trigger-" + blockId);
|
|
8
|
+
if (!$trigger.length) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
var $anchor = $trigger.closest(".ol-feedback-anchor");
|
|
12
|
+
|
|
13
|
+
// Namespace by block id and clear any prior binding so a re-init rebinds
|
|
14
|
+
// idempotently instead of stacking duplicate click handlers.
|
|
15
|
+
$trigger
|
|
16
|
+
.off("click.olFeedback-" + blockId)
|
|
17
|
+
.on("click.olFeedback-" + blockId, function (event) {
|
|
18
|
+
event.stopPropagation();
|
|
19
|
+
// Post only to the known MFE origin; never "*" (would leak block context).
|
|
20
|
+
if (!mfeBaseUrl) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
window.parent.postMessage(
|
|
24
|
+
{ type: "ol-feedback::drawer-open", payload: payload },
|
|
25
|
+
mfeBaseUrl
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Placement: left of the AskTIM trigger when present, else right-aligned.
|
|
30
|
+
var $chatBtn = $("#chat-button-" + blockId);
|
|
31
|
+
if ($chatBtn.length) {
|
|
32
|
+
$anchor.closest(".ol-feedback-container").addClass("ol-feedback-container--relocated");
|
|
33
|
+
$anchor.addClass("ol-feedback-anchor--docked");
|
|
34
|
+
// Problem blocks render a horizontal "saved/submit" notification line that
|
|
35
|
+
// runs behind the button row; flag them so CSS can mask it across the gap.
|
|
36
|
+
if (payload.blockType === "problem") {
|
|
37
|
+
$anchor.addClass("ol-feedback-anchor--line-masked");
|
|
38
|
+
}
|
|
39
|
+
$chatBtn.before($anchor);
|
|
40
|
+
|
|
41
|
+
// AskTIM lifts its button with an out-of-flow offset that varies by block
|
|
42
|
+
// type, so align the megaphone's center to the button's rendered position.
|
|
43
|
+
var alignFrame = null;
|
|
44
|
+
var alignToChatButton = function () {
|
|
45
|
+
$anchor.css("transform", "");
|
|
46
|
+
var btnRect = $chatBtn[0].getBoundingClientRect();
|
|
47
|
+
var anchorRect = $anchor[0].getBoundingClientRect();
|
|
48
|
+
var delta =
|
|
49
|
+
(btnRect.top + btnRect.height / 2) -
|
|
50
|
+
(anchorRect.top + anchorRect.height / 2);
|
|
51
|
+
if (Math.abs(delta) > 0.5) {
|
|
52
|
+
$anchor.css("transform", "translateY(" + delta + "px)");
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var scheduleAlign = function () {
|
|
56
|
+
if (alignFrame) {
|
|
57
|
+
window.cancelAnimationFrame(alignFrame);
|
|
58
|
+
}
|
|
59
|
+
alignFrame = window.requestAnimationFrame(alignToChatButton);
|
|
60
|
+
};
|
|
61
|
+
scheduleAlign();
|
|
62
|
+
// Namespace by block id so re-init rebinds idempotently instead of
|
|
63
|
+
// stacking duplicate handlers; rAF collapses resize bursts to one pass.
|
|
64
|
+
$(window)
|
|
65
|
+
.off("resize.olFeedback-" + blockId)
|
|
66
|
+
.on("resize.olFeedback-" + blockId, scheduleAlign);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function FeedbackAsideView(runtime, element, blockElement, initArgs) {
|
|
71
|
+
initFeedback(initArgs);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
window.FeedbackAsideInit = function (runtime, element, blockElement, initArgs) {
|
|
75
|
+
return new FeedbackAsideView(runtime, element, blockElement, initArgs);
|
|
76
|
+
};
|
|
77
|
+
})($);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Utility helpers for ol_openedx_feedback."""
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
|
|
5
|
+
from ol_openedx_feedback.constants import DEFAULT_EXCLUDED_BLOCK_TYPES
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_excluded_block_types():
|
|
9
|
+
"""Return the block types that never get a feedback trigger.
|
|
10
|
+
|
|
11
|
+
Defaults to structural containers; overridable per deployment via the
|
|
12
|
+
``OL_OPENEDX_FEEDBACK_EXCLUDED_BLOCK_TYPES`` setting.
|
|
13
|
+
"""
|
|
14
|
+
return set(
|
|
15
|
+
getattr(
|
|
16
|
+
settings,
|
|
17
|
+
"OL_OPENEDX_FEEDBACK_EXCLUDED_BLOCK_TYPES",
|
|
18
|
+
DEFAULT_EXCLUDED_BLOCK_TYPES,
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def is_aside_applicable_to_block(block):
|
|
24
|
+
"""Feedback applies to every block type except excluded containers."""
|
|
25
|
+
block_type = getattr(block, "category", None)
|
|
26
|
+
return bool(block_type) and block_type not in get_excluded_block_types()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ol-openedx-feedback
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An Open edX plugin to collect per-block learner feedback
|
|
5
|
+
Author: MIT Office of Digital Learning
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Keywords: Python,edx
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: django>=4.0
|
|
10
|
+
Requires-Dist: xblock
|
|
11
|
+
Description-Content-Type: text/x-rst
|
|
12
|
+
|
|
13
|
+
ol-openedx-feedback
|
|
14
|
+
###################
|
|
15
|
+
|
|
16
|
+
An Open edX plugin that adds a per-block "Send feedback" trigger to applicable
|
|
17
|
+
leaf blocks in the LMS via an ``XBlockAside``. The trigger is shown only to
|
|
18
|
+
authenticated learners (never in Studio author/preview mode and never to
|
|
19
|
+
anonymous users).
|
|
20
|
+
|
|
21
|
+
When a learner clicks the trigger the aside posts an ``ol-feedback::drawer-open``
|
|
22
|
+
message to its parent window (the Learning MFE) using ``window.parent.postMessage``.
|
|
23
|
+
The message payload carries the block context needed to identify the content
|
|
24
|
+
being rated:
|
|
25
|
+
|
|
26
|
+
- ``courseId`` — the course key
|
|
27
|
+
- ``blockUsageKey`` — the block's usage key
|
|
28
|
+
- ``blockType`` — the XBlock category (e.g. ``problem``, ``video``)
|
|
29
|
+
- ``blockDisplayName`` — the block's display name
|
|
30
|
+
|
|
31
|
+
The Learning MFE receives the message and opens the feedback drawer, which
|
|
32
|
+
submits the learner's feedback directly to the **mit-learn** service. This
|
|
33
|
+
plugin does **not** persist anything in edx-platform and exposes no REST API.
|
|
34
|
+
|
|
35
|
+
Installation
|
|
36
|
+
============
|
|
37
|
+
|
|
38
|
+
Install the package into the LMS Python environment:
|
|
39
|
+
|
|
40
|
+
.. code-block:: bash
|
|
41
|
+
|
|
42
|
+
pip install ol-openedx-feedback
|
|
43
|
+
|
|
44
|
+
The plugin registers itself automatically through its entry points — the
|
|
45
|
+
``xblock_asides.v1`` aside plus the ``lms.djangoapp`` app config — so no changes
|
|
46
|
+
to ``INSTALLED_APPS`` are required. Restart the LMS after installing. (The
|
|
47
|
+
trigger is learner-facing only, so the plugin is LMS-only and is not installed
|
|
48
|
+
in Studio/CMS.)
|
|
49
|
+
|
|
50
|
+
Enable XBlock asides in the LMS admin
|
|
51
|
+
-------------------------------------
|
|
52
|
+
|
|
53
|
+
XBlock asides must be turned on for any aside (including this one) to render.
|
|
54
|
+
In the LMS Django admin, open **XBlock Asides Config**
|
|
55
|
+
(``/admin/lms_xblock/xblockasidesconfig/``), add a new entry, and check
|
|
56
|
+
**Enabled**. Make sure the block types you want the feedback trigger on are
|
|
57
|
+
**not** listed in **Disabled blocks** (the space-separated field defaults to
|
|
58
|
+
``about course_info static_tab``).
|
|
59
|
+
|
|
60
|
+
Enablement
|
|
61
|
+
==========
|
|
62
|
+
|
|
63
|
+
Feedback is gated by the ``ol_openedx_feedback.feedback_enabled`` course waffle
|
|
64
|
+
flag (default off). Enable it for the desired courses (or globally) to roll out.
|
|
65
|
+
|
|
66
|
+
Configuration
|
|
67
|
+
=============
|
|
68
|
+
|
|
69
|
+
By default the trigger renders on every leaf block and is suppressed only on
|
|
70
|
+
structural containers (``course`` / ``chapter`` / ``sequential`` / ``vertical``).
|
|
71
|
+
To additionally exclude one or more block types (for example ``html``), override
|
|
72
|
+
the excluded set through ``ENV_TOKENS`` (e.g. in ``lms.yml``):
|
|
73
|
+
|
|
74
|
+
.. code-block:: yaml
|
|
75
|
+
|
|
76
|
+
OL_OPENEDX_FEEDBACK_EXCLUDED_BLOCK_TYPES:
|
|
77
|
+
- course
|
|
78
|
+
- chapter
|
|
79
|
+
- sequential
|
|
80
|
+
- vertical
|
|
81
|
+
- html
|
|
82
|
+
|
|
83
|
+
The plugin reads this value via its ``settings.common`` ``plugin_settings`` hook
|
|
84
|
+
and exposes it as the ``OL_OPENEDX_FEEDBACK_EXCLUDED_BLOCK_TYPES`` Django
|
|
85
|
+
setting, defaulting to the structural set above when unset.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ol_openedx_feedback/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
ol_openedx_feedback/apps.py,sha256=hdc6w--6HoG8Z63bZS5stpw560xqerM7fH76If9d8k4,992
|
|
3
|
+
ol_openedx_feedback/block.py,sha256=spyBzawijV_JON5SN2jkIuWansJVwXSVF4Ytipa9S1A,3394
|
|
4
|
+
ol_openedx_feedback/compat.py,sha256=pbu4K5pxxuYXx2yHcxWFsMbVi21ZSX7EyladXG3979Y,732
|
|
5
|
+
ol_openedx_feedback/constants.py,sha256=b6KN8iITa4GEYlLsw9Tm5wC_llRDxtg8hId1mRN7GpM,351
|
|
6
|
+
ol_openedx_feedback/utils.py,sha256=91SjqmdVexTjiON9qYaRg4j7yt2xhfAokXY3ZmV-GxU,807
|
|
7
|
+
ol_openedx_feedback/settings/common.py,sha256=wwP8RX6MBtuYOvuy82KQjoC4sN4DYRHfXukkyn50FT8,451
|
|
8
|
+
ol_openedx_feedback/static/css/feedback.css,sha256=HrCrKzoQn1L3cpBxHC0DLYsqlpRtdzdF4qdOuDo5hHs,2804
|
|
9
|
+
ol_openedx_feedback/static/html/student_view.html,sha256=LYzEeVqhnkpdYHREM49_5Ci78rycB3RxJ3y2QAm04ho,524
|
|
10
|
+
ol_openedx_feedback/static/js/feedback.js,sha256=uJpXXEbHujhn8O8kTZtWG7pjD0cVKbAtMoQxxwaKl78,2973
|
|
11
|
+
ol_openedx_feedback-0.1.0.dist-info/METADATA,sha256=sZkFn3yL2JozaSXItg6z-X_3gKfaOmjU-wxTkrxUinM,3135
|
|
12
|
+
ol_openedx_feedback-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
13
|
+
ol_openedx_feedback-0.1.0.dist-info/entry_points.txt,sha256=xNggMG22qeCYhbvW2JOpCFHswdEXhZ2T-2cHXkSAnbY,169
|
|
14
|
+
ol_openedx_feedback-0.1.0.dist-info/RECORD,,
|