From 01737d60b28ffe07fc2153cc0394ab72b46c4530 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Wed, 10 Apr 2024 18:10:42 +0930 Subject: [PATCH] feat: adds endpoints for fetchGuestToken * generate_superset_context : adds a superset_guest_token_url to the context * generate_superset_token : called when ^ hit instead of on render. * adds a plugin url and view * removes superset_token from the context * adds/updates tests --- platform_plugin_aspects/apps.py | 9 ++- platform_plugin_aspects/extensions/filters.py | 11 --- .../static/html/superset.html | 4 +- .../static/js/embed_dashboard.js | 19 ++++- platform_plugin_aspects/static/js/superset.js | 4 +- platform_plugin_aspects/tests/test_utils.py | 78 ++++++------------- platform_plugin_aspects/tests/test_views.py | 51 ++++++++++++ platform_plugin_aspects/tests/test_xblock.py | 49 +++++++----- platform_plugin_aspects/urls.py | 20 +++++ platform_plugin_aspects/utils.py | 60 +++++++------- platform_plugin_aspects/views.py | 48 ++++++++++++ platform_plugin_aspects/xblock.py | 43 +++++++++- requirements/test.in | 1 + test_settings.py | 29 ++++++- 14 files changed, 304 insertions(+), 122 deletions(-) create mode 100644 platform_plugin_aspects/tests/test_views.py create mode 100644 platform_plugin_aspects/urls.py create mode 100644 platform_plugin_aspects/views.py diff --git a/platform_plugin_aspects/apps.py b/platform_plugin_aspects/apps.py index fa2b8a4..36da0ea 100644 --- a/platform_plugin_aspects/apps.py +++ b/platform_plugin_aspects/apps.py @@ -3,7 +3,7 @@ """ from django.apps import AppConfig -from edx_django_utils.plugins import PluginSettings, PluginSignals +from edx_django_utils.plugins import PluginSettings, PluginSignals, PluginURLs class PlatformPluginAspectsConfig(AppConfig): @@ -14,6 +14,13 @@ class PlatformPluginAspectsConfig(AppConfig): name = "platform_plugin_aspects" plugin_app = { + PluginURLs.CONFIG: { + "lms.djangoapp": { + PluginURLs.NAMESPACE: name, + PluginURLs.REGEX: r"^aspects/", + PluginURLs.RELATIVE_PATH: "urls", + }, + }, PluginSettings.CONFIG: { "lms.djangoapp": { "production": {PluginSettings.RELATIVE_PATH: "settings.production"}, diff --git a/platform_plugin_aspects/extensions/filters.py b/platform_plugin_aspects/extensions/filters.py index 428d53c..90b7c01 100644 --- a/platform_plugin_aspects/extensions/filters.py +++ b/platform_plugin_aspects/extensions/filters.py @@ -14,12 +14,6 @@ TEMPLATE_ABSOLUTE_PATH = "/instructor_dashboard/" BLOCK_CATEGORY = "aspects" -ASPECTS_SECURITY_FILTERS_FORMAT = [ - "org = '{course.org}'", - "course_name = '{course.display_name}'", - "course_run = '{course.id.run}'", -] - class AddSupersetTab(PipelineStep): """ @@ -36,9 +30,6 @@ def run_filter( """ course = context["course"] dashboards = settings.ASPECTS_INSTRUCTOR_DASHBOARDS - extra_filters_format = settings.SUPERSET_EXTRA_FILTERS_FORMAT - - filters = ASPECTS_SECURITY_FILTERS_FORMAT + extra_filters_format user = get_current_user() @@ -51,9 +42,7 @@ def run_filter( context = generate_superset_context( context, - user, dashboards=dashboards, - filters=filters, language=formatted_language, ) diff --git a/platform_plugin_aspects/static/html/superset.html b/platform_plugin_aspects/static/html/superset.html index 2782da9..fd5e9fb 100644 --- a/platform_plugin_aspects/static/html/superset.html +++ b/platform_plugin_aspects/static/html/superset.html @@ -10,7 +10,7 @@

{{display_name}}

{{exception}}

{% elif not superset_dashboards %}

Dashboard UUID is not set. Please set the dashboard UUID in the Studio.

- {% elif superset_url and superset_token %} {% if xblock_id %} + {% elif superset_url and superset_guest_token_url %} {% if xblock_id %}
{% else %}
@@ -31,7 +31,7 @@

{{display_name}}

{% endif %}
diff --git a/platform_plugin_aspects/static/js/embed_dashboard.js b/platform_plugin_aspects/static/js/embed_dashboard.js index 1013001..0a26b44 100644 --- a/platform_plugin_aspects/static/js/embed_dashboard.js +++ b/platform_plugin_aspects/static/js/embed_dashboard.js @@ -1,11 +1,24 @@ -function embedDashboard(dashboard_uuid, superset_url, superset_token, xblock_id) { +function embedDashboard(dashboard_uuid, superset_url, guest_token_url, xblock_id) { xblock_id = xblock_id || ""; + + async fetchGuestToken() { + // Fetch the guest token from your backend + const response = await fetch(guest_token_url, { + method: 'POST', + body: JSON.stringify({ + // TODO csrf_token: csrf_token, + }) + }); + const data = await response.json(); + return data.guestToken; + } + window.supersetEmbeddedSdk .embedDashboard({ id: dashboard_uuid, // given by the Superset embedding UI supersetDomain: superset_url, // your Superset instance mountPoint: document.getElementById(`superset-embedded-container-${xblock_id}`), // any html element that can contain an iframe - fetchGuestToken: () => superset_token, // function that returns a Promise with the guest token + fetchGuestToken: fetchGuestToken, dashboardUiConfig: { // dashboard UI config: hideTitle, hideTab, hideChartControls, filters.visible, filters.expanded (optional) hideTitle: true, @@ -28,6 +41,6 @@ function embedDashboard(dashboard_uuid, superset_url, superset_token, xblock_id) if (window.superset_dashboards !== undefined) { window.superset_dashboards.forEach(function(dashboard) { - embedDashboard(dashboard.uuid, window.superset_url, window.superset_token, dashboard.uuid); + embedDashboard(dashboard.uuid, window.superset_url, window.superset_guest_token_url); }); } diff --git a/platform_plugin_aspects/static/js/superset.js b/platform_plugin_aspects/static/js/superset.js index 14ae644..392630c 100644 --- a/platform_plugin_aspects/static/js/superset.js +++ b/platform_plugin_aspects/static/js/superset.js @@ -2,11 +2,11 @@ function SupersetXBlock(runtime, element, context) { const dashboard_uuid = context.dashboard_uuid; const superset_url = context.superset_url; - const superset_token = context.superset_token; + const superset_guest_token_url = context.superset_guest_token_url; const xblock_id = context.xblock_id function initSuperset(supersetEmbeddedSdk) { - embedDashboard(dashboard_uuid, superset_url, superset_token, xblock_id); + embedDashboard(dashboard_uuid, superset_url, superset_guest_token_url, xblock_id); } if (typeof require === "function") { diff --git a/platform_plugin_aspects/tests/test_utils.py b/platform_plugin_aspects/tests/test_utils.py index 3839373..0b7f95a 100644 --- a/platform_plugin_aspects/tests/test_utils.py +++ b/platform_plugin_aspects/tests/test_utils.py @@ -2,19 +2,19 @@ Test utils. """ -from collections import namedtuple from unittest.mock import Mock, patch from django.conf import settings from django.test import TestCase from platform_plugin_aspects.utils import ( + generate_guest_token, generate_superset_context, get_ccx_courses, get_model, ) -User = namedtuple("User", ["username"]) +COURSE_ID = "course-v1:org+course+run" class TestUtils(TestCase): @@ -104,15 +104,11 @@ def test_get_ccx_courses_feature_disabled(self): "password": "superset", }, ) - @patch("platform_plugin_aspects.utils._generate_guest_token") - def test_generate_superset_context(self, mock_generate_guest_token): + def test_generate_superset_context(self): """ Test generate_superset_context """ - course_mock = Mock() - filter_mock = Mock() - user_mock = Mock() - context = {"course": course_mock} + context = {"course": COURSE_ID} dashboards = settings.ASPECTS_INSTRUCTOR_DASHBOARDS dashboards.append( @@ -123,42 +119,41 @@ def test_generate_superset_context(self, mock_generate_guest_token): } ) - mock_generate_guest_token.return_value = ("test-token", dashboards) - context = generate_superset_context( context, - user_mock, dashboards=dashboards, - filters=[filter_mock], language="en_US", ) - self.assertEqual(context["superset_token"], "test-token") + self.assertEqual( + context["superset_guest_token_url"], f"/superset_guest_token/{COURSE_ID}" + ) self.assertEqual(context["superset_dashboards"], dashboards) self.assertEqual(context["superset_url"], "http://superset-dummy-url/") + self.assertNotIn("superset_token", context) self.assertNotIn("exception", context) @patch("platform_plugin_aspects.utils.SupersetClient") - def test_generate_superset_context_with_superset_client_exception( + def test_generate_guest_token_with_superset_client_exception( self, mock_superset_client ): """ - Test generate_superset_context + Test generate_guest_token """ - course_mock = Mock() filter_mock = Mock() user_mock = Mock() - context = {"course": course_mock} mock_superset_client.side_effect = Exception("test-exception") - context = generate_superset_context( - context, - user_mock, + token, exception = generate_guest_token( + user=user_mock, + course=COURSE_ID, dashboards=[{"name": "test", "uuid": "test-dashboard-uuid"}], filters=[filter_mock], ) - self.assertIn("exception", context) + mock_superset_client.assert_called_once() + self.assertIsNone(token) + self.assertEqual(str(exception), "test-exception") @patch.object( settings, @@ -171,49 +166,26 @@ def test_generate_superset_context_with_superset_client_exception( }, ) @patch("platform_plugin_aspects.utils.SupersetClient") - def test_generate_superset_context_succesful(self, mock_superset_client): + def test_generate_guest_token_succesful(self, mock_superset_client): """ - Test generate_superset_context + Test generate_guest_token """ - course_mock = Mock() - filter_mock = Mock() - user_mock = Mock() - user_mock.username = "test-user" - context = {"course": course_mock} response_mock = Mock(status_code=200) mock_superset_client.return_value.session.post.return_value = response_mock response_mock.json.return_value = { "token": "test-token", } - dashboards = [{"name": "test", "uuid": "test-dashboard-uuid"}] - - context = generate_superset_context( - context, - user_mock, - dashboards=dashboards, - filters=[filter_mock], - ) - - self.assertEqual(context["superset_token"], "test-token") - self.assertEqual(context["superset_dashboards"], dashboards) - self.assertEqual(context["superset_url"], "http://dummy-superset-url/") - - def test_generate_superset_context_with_exception(self): - """ - Test generate_superset_context - """ - course_mock = Mock() filter_mock = Mock() user_mock = Mock() - user_mock.username = "test-user" - context = {"course": course_mock} + dashboards = [{"name": "test", "uuid": "test-dashboard-uuid"}] - context = generate_superset_context( - context, - user_mock, - dashboards=[{"name": "test", "uuid": "test-dashboard-uuid"}], + token, _errors = generate_guest_token( + user=user_mock, + course=COURSE_ID, + dashboards=dashboards, filters=[filter_mock], ) - self.assertIn("exception", context) + mock_superset_client.assert_called_once() + self.assertEqual(token, "test-token") diff --git a/platform_plugin_aspects/tests/test_views.py b/platform_plugin_aspects/tests/test_views.py new file mode 100644 index 0000000..1b7c2c2 --- /dev/null +++ b/platform_plugin_aspects/tests/test_views.py @@ -0,0 +1,51 @@ +""" +Test views. +""" + +from unittest.mock import patch + +from django.contrib.auth import get_user_model +from django.test import TestCase +from django.urls import reverse + +COURSE_ID = "course-v1:org+course+run" +User = get_user_model() + + +class ViewsTestCase(TestCase): + """ + Test cases for the plugin views and URLs. + """ + + def setUp(self): + """ + Set up data used by multiple tests. + """ + super().setUp() + self.superset_guest_token_url = reverse( + "superset_guest_token", + kwargs={"course_id": COURSE_ID}, + ) + self.user = User.objects.create( + username="user", + email="user@example.com", + ) + self.user.set_password("password") + self.user.save() + + def test_guest_token_requires_authorization(self): + """ + Unauthenticated hits to the endpoint redirect to login. + """ + response = self.client.post(self.superset_guest_token_url) + self.assertEqual(response.status_code, 302) + self.assertIn("login", response.url) + + @patch("platform_plugin_aspects.views.generate_guest_token") + def test_guest_token(self, mock_generate_guest_token): + mock_generate_guest_token.return_value = ("test-token", "test-dashboard-uuid") + self.client.login(username="user", password="password") + response = self.client.post(self.superset_guest_token_url, follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json().get("guestToken"), "test-token") + mock_generate_guest_token.assert_called_once() diff --git a/platform_plugin_aspects/tests/test_xblock.py b/platform_plugin_aspects/tests/test_xblock.py index 2355110..552713f 100644 --- a/platform_plugin_aspects/tests/test_xblock.py +++ b/platform_plugin_aspects/tests/test_xblock.py @@ -2,10 +2,12 @@ """ Test basic SupersetXBlock display function """ +import json from unittest import TestCase from unittest.mock import Mock, patch from opaque_keys.edx.locator import CourseLocator +from webob import Request from xblock.field_data import DictFieldData from xblock.reference.user_service import XBlockUser @@ -35,11 +37,21 @@ def service(block, service): # pylint: disable=unused-argument def local_resource_url(_self, url): return url + def handler_url(_self, handler, *args, **kwargs): + """ + Mock runtime.handlerUrl + + The LMS and CMS runtimes implement handlerUrl, but we have to mock it here. + """ + return f"/{handler}" + runtime = Mock( course_id=course_id, service=service, local_resource_url=Mock(side_effect=local_resource_url), + handlerUrl=Mock(side_effect=handler_url), ) + scope_ids = Mock() field_data = DictFieldData(kwargs) xblock = SupersetXBlock(runtime, field_data, scope_ids) @@ -52,21 +64,12 @@ class TestRender(TestCase): Test the HTML rendering of the XBlock """ - @patch("platform_plugin_aspects.utils.SupersetClient") - def test_render_instructor(self, mock_superset_client): + def test_render_instructor(self): """ Ensure staff can see the Superset dashboard. """ - mock_superset_client.return_value = Mock( - session=Mock( - post=Mock( - return_value=Mock(json=Mock(return_value={"token": "test_token"})) - ) - ) - ) xblock = make_an_xblock("instructor") student_view = xblock.student_view() - mock_superset_client.assert_called_once() html = student_view.content self.assertIsNotNone(html) self.assertIn( @@ -87,14 +90,10 @@ def test_render_student(self): @patch("platform_plugin_aspects.xblock.pkg_resources.resource_exists") @patch("platform_plugin_aspects.xblock.translation.get_language") - @patch("platform_plugin_aspects.utils._generate_guest_token") - def test_render_translations( - self, mock_generate_guest_token, mock_get_language, mock_resource_exists - ): + def test_render_translations(self, mock_get_language, mock_resource_exists): """ Ensure translated javascript is served. """ - mock_generate_guest_token.return_value = ("test-token", "test-dashboard-uuid") mock_get_language.return_value = "eo" mock_resource_exists.return_value = True xblock = make_an_xblock("instructor") @@ -104,20 +103,34 @@ def test_render_translations( url_resource = resource self.assertIsNotNone(url_resource, "No 'url' resource found in fragment") self.assertIn("eo/text.js", url_resource.data) + mock_get_language.assert_called_once() + mock_resource_exists.assert_called_once() @patch("platform_plugin_aspects.xblock.translation.get_language") - @patch("platform_plugin_aspects.utils._generate_guest_token") def test_render_no_translations( self, - mock_generate_guest_token, mock_get_language, ): """ Ensure translated javascript is served. """ - mock_generate_guest_token.return_value = ("test-token", "test-dashboard-uuid") mock_get_language.return_value = None xblock = make_an_xblock("instructor") student_view = xblock.student_view() for resource in student_view.resources: assert resource.kind != "url" + mock_get_language.assert_called_once() + + @patch("platform_plugin_aspects.xblock.generate_guest_token") + def test_guest_token_handler(self, mock_generate_guest_token): + mock_generate_guest_token.return_value = ("test-token", "test-dashboard-uuid") + request = Request.blank("/") + request.method = "POST" + request.body = b"{}" + xblock = make_an_xblock("instructor") + response = xblock.get_superset_guest_token(request) + + assert response.status_code == 200 + data = json.loads(response.body.decode("utf-8")) + assert data.get("guestToken") == "test-token" + mock_generate_guest_token.assert_called_once() diff --git a/platform_plugin_aspects/urls.py b/platform_plugin_aspects/urls.py new file mode 100644 index 0000000..b142e4e --- /dev/null +++ b/platform_plugin_aspects/urls.py @@ -0,0 +1,20 @@ +""" +Urls for the Aspects plugin. +""" + +from django.urls import re_path + +from . import views + +# Copied from openedx.core.constants +COURSE_ID_PATTERN = r"(?P[^/+]+(/|\+)[^/+]+(/|\+)[^/?]+)" + +app_name = "platform_plugin_aspects" + +urlpatterns = [ + re_path( + rf"superset_guest_token/{COURSE_ID_PATTERN}/?$", + views.superset_guest_token, + name="superset_guest_token", + ), +] diff --git a/platform_plugin_aspects/utils.py b/platform_plugin_aspects/utils.py index 849252f..24277b2 100644 --- a/platform_plugin_aspects/utils.py +++ b/platform_plugin_aspects/utils.py @@ -10,6 +10,7 @@ from importlib import import_module from django.conf import settings +from django.urls import NoReverseMatch, reverse from supersetapiclient.client import SupersetClient from xblock.reference.user_service import XBlockUser @@ -26,18 +27,16 @@ def _(text): return text -def generate_superset_context( # pylint: disable=dangerous-default-value +def generate_superset_context( context, - user, dashboards, - filters=[], language=None, ): """ Update context with superset token and dashboard id. Args: - context (dict): the context for the instructor dashboard. It must include a course object + context (dict): the context for the instructor dashboard. It must include a course ID user (XBlockUser or User): the current user. superset_config (dict): superset config. dashboards (list): list of superset dashboard uuid. @@ -49,50 +48,57 @@ def generate_superset_context( # pylint: disable=dangerous-default-value if language: for dashboard in dashboards: - if not dashboard["allow_translations"]: + if not dashboard.get("allow_translations"): continue dashboard["slug"] = f"{dashboard['slug']}-{language}" dashboard["uuid"] = str(get_uuid5(dashboard["uuid"], language)) - superset_token, dashboards = _generate_guest_token( - user=user, - course=course, - superset_config=superset_config, - dashboards=dashboards, - filters=filters, - ) + superset_url = _fix_service_url(superset_config.get("service_url")) - if superset_token: - superset_url = _fix_service_url(superset_config.get("service_url")) - context.update( - { - "superset_token": superset_token, - "superset_dashboards": dashboards, - "superset_url": superset_url, - } + # FIXME -- namespace issue with plugin-registered urls? + try: + guest_token_url = reverse( + "platform_plugin_aspects:superset_guest_token", + kwargs={"course_id": course}, ) - else: - context.update( - { - "exception": str(dashboards), - } + except NoReverseMatch: + logger.error( + "Error reversing platform_plugin_aspects:superset_guest_token, trying without namespace" ) + try: + guest_token_url = reverse( + "superset_guest_token", + kwargs={"course_id": course}, + ) + logger.info("Reversing superset_guest_token worked") + except NoReverseMatch: + logger.critical("Error reversing superset_guest_token, giving up") + guest_token_url = "" + + context.update( + { + "superset_dashboards": dashboards, + "superset_url": superset_url, + "superset_guest_token_url": guest_token_url, + } + ) return context -def _generate_guest_token(user, course, superset_config, dashboards, filters): +def generate_guest_token(user, course, dashboards, filters): """ Generate a Superset guest token for the user. Args: user: User object. - course: Course object. + course: string Course ID Returns: tuple: Superset guest token and dashboard id. or None, exception if Superset is missconfigured or cannot generate guest token. """ + superset_config = settings.SUPERSET_CONFIG superset_internal_host = _fix_service_url( superset_config.get("internal_service_url") or superset_config.get("service_url") diff --git a/platform_plugin_aspects/views.py b/platform_plugin_aspects/views.py new file mode 100644 index 0000000..38d739f --- /dev/null +++ b/platform_plugin_aspects/views.py @@ -0,0 +1,48 @@ +""" +Endpoints for the Aspects platform plugin. +""" + +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.core.exceptions import ImproperlyConfigured +from django.http import JsonResponse +from django.views.decorators.http import require_POST +from opaque_keys.edx.keys import CourseKey + +from .utils import _, generate_guest_token + + +@require_POST +@login_required +def superset_guest_token(request, course_id): + """ + Return a guest token for Superset that the given request.user can use to access the Superset API. + """ + # TODO -- restrict access to course staff? how? + + course_key = CourseKey.from_string(course_id) + built_in_filters = [ + f"org = '{course_key.org}'", + # TODO? Need to fetch the Course object to add this filter. + # f"course_name = '{course_key.display_name}'", + f"course_run = '{course_key.run}'", + ] + dashboards = settings.ASPECTS_INSTRUCTOR_DASHBOARDS + extra_filters_format = settings.SUPERSET_EXTRA_FILTERS_FORMAT + + guest_token, exception = generate_guest_token( + user=request.user, + course=course_id, + dashboards=dashboards, + filters=built_in_filters + extra_filters_format, + ) + + if not guest_token: + raise ImproperlyConfigured( + _( + "Unable to fetch Superset guest token, " + "mostly likely due to invalid settings.SUPERSET_CONFIG: {exception}" + ).format(exception=exception) + ) + + return JsonResponse({"guestToken": guest_token}) diff --git a/platform_plugin_aspects/xblock.py b/platform_plugin_aspects/xblock.py index 969afb5..06eb462 100644 --- a/platform_plugin_aspects/xblock.py +++ b/platform_plugin_aspects/xblock.py @@ -2,17 +2,19 @@ from __future__ import annotations +import json import logging import pkg_resources from django.utils import translation from web_fragments.fragment import Fragment -from xblock.core import XBlock +from webob import Response +from xblock.core import JsonHandlerError, XBlock from xblock.fields import List, Scope, String from xblock.utils.resources import ResourceLoader from xblock.utils.studio_editable import StudioEditableXBlockMixin -from .utils import _, generate_superset_context +from .utils import _, generate_guest_token, generate_superset_context log = logging.getLogger(__name__) loader = ResourceLoader(__name__) @@ -112,12 +114,15 @@ def student_view(self, context=None): context = generate_superset_context( context=context, - user=user, dashboards=self.dashboards(), - filters=self.filters, ) context["xblock_id"] = str(self.scope_ids.usage_id.block_id) + # Use our xblock handler instead of the platform plugin's view. + context["superset_guest_token_url"] = self.runtime.handlerUrl( + self, "get_superset_guest_token" + ) + frag = Fragment() frag.add_content(self.render_template("static/html/superset.html", context)) frag.add_css(loader.load_unicode("static/css/superset.css")) @@ -180,3 +185,33 @@ def _get_statici18n_js_url(): ): return text_js.format(locale_code=code) return None + + @XBlock.json_handler + def get_superset_guest_token( + self, request_body, suffix="" + ): # pylint: disable=unused-argument + """Return a guest token for Superset.""" + user_service = self.runtime.service(self, "user") + user = user_service.get_current_user() + + guest_token, exception = generate_guest_token( + user=user, + course=self.runtime.course_id, + dashboards=self.dashboards(), + filters=self.filters, + ) + + if not guest_token: + raise JsonHandlerError( + 500, + _( + "Unable to fetch Superset guest token, " + "mostly likely due to invalid settings.SUPERSET_CONFIG: {exception}" + ).format(exception=exception), + ) + + return Response( + json.dumps({"guestToken": guest_token}), + content_type="application/json", + charset="UTF-8", + ) diff --git a/requirements/test.in b/requirements/test.in index 1bc4cb8..9c69ca6 100644 --- a/requirements/test.in +++ b/requirements/test.in @@ -9,3 +9,4 @@ code-annotations # provides commands used by the pii_check make target. responses # mocks for the requests library ddt django-mock-queries +xblock-sdk # for testing XBlock handlers diff --git a/test_settings.py b/test_settings.py index 987b008..14a50ab 100644 --- a/test_settings.py +++ b/test_settings.py @@ -24,9 +24,28 @@ }, } -INSTALLED_APPS = ("platform_plugin_aspects",) +INSTALLED_APPS = ( + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "platform_plugin_aspects", +) + +ROOT_URLCONF = 'platform_plugin_aspects.urls' + +SECRET_KEY = 'very-insecure-key' + +MIDDLEWARE = [ + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +] EVENT_SINK_CLICKHOUSE_MODEL_CONFIG = { + "auth_user": { + "module": "django.contrib.auth.models", + "model": "User", + }, "user_profile": { "module": "common.djangoapps.student.models", "model": "UserProfile", @@ -35,6 +54,14 @@ "module": "openedx.core.djangoapps.content.course_overviews.models", "model": "CourseOverview", }, + "external_id": { + "module": "openedx.core.djangoapps.external_user_ids.models", + "model": "ExternalId", + }, + "user_preference": { + "module": "openedx.core.djangoapps.user_api.models", + "model": "UserPreference", + }, } EVENT_SINK_CLICKHOUSE_COURSE_OVERVIEWS_ENABLED = True