Skip to content

Commit 3aa976b

Browse files
committed
feat!: Remove outdated Libraries Relaunch cruft
The V2 libraries project had a few past iterations which were never launched. This commit cleans up pieces from those which we don't need for the real Libraries Relaunch MVP in Sumac: * Remove ENABLE_LIBRARY_AUTHORING_MICROFRONTEND, LIBRARY_AUTHORING_FRONTEND_URL, and REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND, all of which are obsolete now that library authoring has been merged into https://github.com/openedx/frontend-app-authoring. More details on the new Content Libraries configuration settings are here: openedx/frontend-app-authoring#1334 * Remove dangling support for syncing V2 (learning core-backed) library content using the LibraryContentBlock. This code was all based on an older understanding of V2 Content Libraries, where the libraries were smaller and versioned as a whole rather then versioned by-item. Reference to V2 libraries will be done on a per-block basis using the upstream/downstream system, described here: https://github.com/openedx/edx-platform/blob/master/docs/decisions/0020-upstream-downstream.rst It's important that we remove this support now so that OLX course authors don't stuble upon it and use it, which would be buggy and complicate future migrations. * Remove the "mode" parameter from LibraryContentBlock. The only supported mode was and is "random". We will not be adding any further modes. Going forward for V2, we will have an ItemBank block for randomizing items (regardless of source), which can be synthesized with upstream referenced as described above. Existing LibraryContentBlocks will be migrated. * Finally, some renamings: * LibraryContentBlock -> LegacyLibraryContentBlock * LibraryToolsService -> LegacyLibraryToolsService * LibrarySummary -> LegacyLibrarySummary Module names and the old OLX tag (library_content) are unchanged. Closes: openedx/frontend-app-authoring#1115
1 parent 5b80967 commit 3aa976b

File tree

31 files changed

+181
-605
lines changed

31 files changed

+181
-605
lines changed

cms/djangoapps/contentstore/config/waffle.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"""
55

66

7-
from edx_toggles.toggles import WaffleFlag, WaffleSwitch
7+
from edx_toggles.toggles import WaffleSwitch
88

99
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
1010

@@ -26,20 +26,6 @@
2626
f'{WAFFLE_NAMESPACE}.show_review_rules', __name__, LOG_PREFIX
2727
)
2828

29-
# Waffle flag to redirect to the library authoring MFE.
30-
# .. toggle_name: contentstore.library_authoring_mfe
31-
# .. toggle_implementation: WaffleFlag
32-
# .. toggle_default: False
33-
# .. toggle_description: Toggles the new micro-frontend-based implementation of the library authoring experience.
34-
# .. toggle_use_cases: temporary, open_edx
35-
# .. toggle_creation_date: 2020-08-03
36-
# .. toggle_target_removal_date: 2020-12-31
37-
# .. toggle_warning: Also set settings.LIBRARY_AUTHORING_MICROFRONTEND_URL and ENABLE_LIBRARY_AUTHORING_MICROFRONTEND.
38-
# .. toggle_tickets: https://openedx.atlassian.net/wiki/spaces/OEPM/pages/4106944527/Libraries+Relaunch+Proposal+For+Product+Review
39-
REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND = WaffleFlag(
40-
f'{WAFFLE_NAMESPACE}.library_authoring_mfe', __name__, LOG_PREFIX
41-
)
42-
4329

4430
# .. toggle_name: studio.custom_relative_dates
4531
# .. toggle_implementation: CourseWaffleFlag

cms/djangoapps/contentstore/rest_api/v1/serializers/home.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,8 @@ class CourseHomeSerializer(serializers.Serializer):
5959
libraries = LibraryViewSerializer(many=True, required=False, allow_null=True)
6060
libraries_enabled = serializers.BooleanField()
6161
taxonomies_enabled = serializers.BooleanField()
62-
library_authoring_mfe_url = serializers.CharField()
6362
taxonomy_list_mfe_url = serializers.CharField()
6463
optimization_enabled = serializers.BooleanField()
65-
redirect_to_library_authoring_mfe = serializers.BooleanField()
6664
request_course_creator_url = serializers.CharField()
6765
rerun_creator_status = serializers.BooleanField()
6866
show_new_library_button = serializers.BooleanField()

cms/djangoapps/contentstore/rest_api/v1/views/home.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,7 @@ def get(self, request: Request):
5959
"in_process_course_actions": [],
6060
"libraries": [],
6161
"libraries_enabled": true,
62-
"library_authoring_mfe_url": "//localhost:3001/course/course-v1:edX+P315+2T2023",
6362
"optimization_enabled": true,
64-
"redirect_to_library_authoring_mfe": false,
6563
"request_course_creator_url": "/request_course_creator",
6664
"rerun_creator_status": true,
6765
"show_new_library_button": true,

cms/djangoapps/contentstore/rest_api/v1/views/tests/test_home.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,8 @@ def test_home_page_courses_response(self):
5252
"libraries": [],
5353
"libraries_enabled": True,
5454
"taxonomies_enabled": True,
55-
"library_authoring_mfe_url": settings.LIBRARY_AUTHORING_MICROFRONTEND_URL,
5655
"taxonomy_list_mfe_url": 'http://course-authoring-mfe/taxonomies',
5756
"optimization_enabled": False,
58-
"redirect_to_library_authoring_mfe": False,
5957
"request_course_creator_url": "/request_course_creator",
6058
"rerun_creator_status": True,
6159
"show_new_library_button": True,

cms/djangoapps/contentstore/utils.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
)
9999
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
100100
from cms.djangoapps.models.settings.course_metadata import CourseMetadata
101-
from xmodule.library_tools import LibraryToolsService
101+
from xmodule.library_tools import LegacyLibraryToolsService
102102
from xmodule.course_block import DEFAULT_START_DATE # lint-amnesty, pylint: disable=wrong-import-order
103103
from xmodule.data import CertificatesDisplayBehaviors
104104
from xmodule.modulestore import ModuleStoreEnum # lint-amnesty, pylint: disable=wrong-import-order
@@ -1265,7 +1265,7 @@ def load_services_for_studio(runtime, user):
12651265
"settings": SettingsService(),
12661266
"lti-configuration": ConfigurationService(CourseAllowPIISharingInLTIFlag),
12671267
"teams_configuration": TeamsConfigurationService(),
1268-
"library_tools": LibraryToolsService(modulestore(), user.id)
1268+
"library_tools": LegacyLibraryToolsService(modulestore(), user.id)
12691269
}
12701270

12711271
runtime._services.update(services) # lint-amnesty, pylint: disable=protected-access
@@ -1671,9 +1671,7 @@ def get_home_context(request, no_course=False):
16711671
ENABLE_GLOBAL_STAFF_OPTIMIZATION,
16721672
)
16731673
from cms.djangoapps.contentstore.views.library import (
1674-
LIBRARY_AUTHORING_MICROFRONTEND_URL,
16751674
LIBRARIES_ENABLED,
1676-
should_redirect_to_library_authoring_mfe,
16771675
user_can_view_create_library_button,
16781676
)
16791677

@@ -1699,12 +1697,9 @@ def get_home_context(request, no_course=False):
16991697
'in_process_course_actions': in_process_course_actions,
17001698
'libraries_enabled': LIBRARIES_ENABLED,
17011699
'taxonomies_enabled': not is_tagging_feature_disabled(),
1702-
'redirect_to_library_authoring_mfe': should_redirect_to_library_authoring_mfe(),
1703-
'library_authoring_mfe_url': LIBRARY_AUTHORING_MICROFRONTEND_URL,
17041700
'taxonomy_list_mfe_url': get_taxonomy_list_url(),
17051701
'libraries': libraries,
1706-
'show_new_library_button': user_can_view_create_library_button(user)
1707-
and not should_redirect_to_library_authoring_mfe(),
1702+
'show_new_library_button': user_can_view_create_library_button(user),
17081703
'user': user,
17091704
'request_course_creator_url': reverse('request_course_creator'),
17101705
'course_creator_status': _get_course_creator_status(user),
@@ -2202,7 +2197,7 @@ class StudioPermissionsService:
22022197
22032198
Deprecated. To be replaced by a more general authorization service.
22042199
2205-
Only used by LibraryContentBlock (and library_tools.py).
2200+
Only used by LegacyLibraryContentBlock (and library_tools.py).
22062201
"""
22072202

22082203
def __init__(self, user):

cms/djangoapps/contentstore/views/library.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
)
4242
from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadRequest, expect_json
4343

44-
from ..config.waffle import REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND
4544
from ..utils import add_instructor, reverse_library_url
4645
from .component import CONTAINER_TEMPLATES, get_component_templates
4746
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import create_xblock_info
@@ -52,21 +51,6 @@
5251
log = logging.getLogger(__name__)
5352

5453
LIBRARIES_ENABLED = settings.FEATURES.get('ENABLE_CONTENT_LIBRARIES', False)
55-
ENABLE_LIBRARY_AUTHORING_MICROFRONTEND = settings.FEATURES.get('ENABLE_LIBRARY_AUTHORING_MICROFRONTEND', False)
56-
LIBRARY_AUTHORING_MICROFRONTEND_URL = settings.LIBRARY_AUTHORING_MICROFRONTEND_URL
57-
58-
59-
def should_redirect_to_library_authoring_mfe():
60-
"""
61-
Boolean helper method, returns whether or not to redirect to the Library
62-
Authoring MFE based on settings and flags.
63-
"""
64-
65-
return (
66-
ENABLE_LIBRARY_AUTHORING_MICROFRONTEND and
67-
LIBRARY_AUTHORING_MICROFRONTEND_URL and
68-
REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND.is_enabled()
69-
)
7054

7155

7256
def _user_can_create_library_for_org(user, org=None):

cms/djangoapps/contentstore/views/tests/test_block.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,7 @@ def test_shallow_duplicate(self):
982982

983983
def test_duplicate_library_content_block(self): # pylint: disable=too-many-statements
984984
"""
985-
Test the LibraryContentBlock's special duplication process.
985+
Test the LegacyLibraryContentBlock's special duplication process.
986986
"""
987987
store = modulestore()
988988

cms/djangoapps/contentstore/views/tests/test_clipboard_paste.py

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
from opaque_keys.edx.keys import UsageKey
88
from rest_framework.test import APIClient
99
from openedx_tagging.core.tagging.models import Tag
10-
from organizations.models import Organization
1110
from xmodule.modulestore.django import contentstore, modulestore
1211
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, upload_file_to_course
13-
from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, ToyCourseFactory
12+
from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory, ToyCourseFactory, LibraryFactory
1413

1514
from cms.djangoapps.contentstore.utils import reverse_usage_url
16-
from openedx.core.djangoapps.content_libraries import api as library_api
1715
from openedx.core.djangoapps.content_tagging import api as tagging_api
1816

1917
CLIPBOARD_ENDPOINT = "/api/content-staging/v1/clipboard/"
@@ -165,12 +163,12 @@ def _setup_tagged_content(self, course_key) -> dict:
165163
publish_item=True,
166164
).location
167165

168-
library = ClipboardLibraryContentPasteTestCase.setup_library()
166+
library = ClipboardPasteFromV1LibraryTestCase.setup_library()
169167
with self.store.bulk_operations(course_key):
170168
library_content_block_key = BlockFactory.create(
171169
parent=self.store.get_item(unit_key),
172170
category="library_content",
173-
source_library_id=str(library.key),
171+
source_library_id=str(library.context_key),
174172
display_name="LC Block",
175173
publish_item=True,
176174
).location
@@ -393,27 +391,27 @@ def test_paste_with_assets(self):
393391
assert source_pic2_hash != dest_pic2_hash # Because there was a conflict, this file was unchanged.
394392

395393

396-
class ClipboardLibraryContentPasteTestCase(ModuleStoreTestCase):
394+
class ClipboardPasteFromV1LibraryTestCase(ModuleStoreTestCase):
397395
"""
398-
Test Clipboard Paste functionality with library content
396+
Test Clipboard Paste functionality with legacy (v1) library content
399397
"""
400398

401399
def setUp(self):
402400
"""
403-
Set up a v2 Content Library and a library content block
401+
Set up a v1 Content Library and a library content block
404402
"""
405403
super().setUp()
406404
self.client = APIClient()
407405
self.client.login(username=self.user.username, password=self.user_password)
408406
self.store = modulestore()
409-
library = self.setup_library()
407+
self.library = self.setup_library()
410408

411409
# Create a library content block (lc), point it out our library, and sync it.
412410
self.course = CourseFactory.create(display_name='Course')
413411
self.orig_lc_block = BlockFactory.create(
414412
parent=self.course,
415413
category="library_content",
416-
source_library_id=str(library.key),
414+
source_library_id=str(self.library.context_key),
417415
display_name="LC Block",
418416
publish_item=False,
419417
)
@@ -426,28 +424,25 @@ def setUp(self):
426424
@classmethod
427425
def setup_library(cls):
428426
"""
429-
Creates and returns a content library.
427+
Creates and returns a legacy content library with 1 problem
430428
"""
431-
library = library_api.create_library(
432-
library_type=library_api.COMPLEX,
433-
org=Organization.objects.create(name="Test Org", short_name="CL-TEST"),
434-
slug="lib",
435-
title="Library",
436-
)
437-
# Populate it with a problem:
438-
problem_key = library_api.create_library_block(library.key, "problem", "p1").usage_key
439-
library_api.set_library_block_olx(problem_key, """
440-
<problem display_name="MCQ" max_attempts="1">
429+
library = LibraryFactory.create(display_name='Library')
430+
lib_block = BlockFactory.create(
431+
parent_location=library.usage_key,
432+
category="problem",
433+
display_name="MCQ",
434+
max_attempts=1,
435+
data="""
441436
<multiplechoiceresponse>
442437
<label>Q</label>
443438
<choicegroup type="MultipleChoice">
444439
<choice correct="false">Wrong</choice>
445440
<choice correct="true">Right</choice>
446441
</choicegroup>
447442
</multiplechoiceresponse>
448-
</problem>
449-
""")
450-
library_api.publish_changes(library.key)
443+
""",
444+
publish_item=False,
445+
)
451446
return library
452447

453448
def test_paste_library_content_block(self):

cms/envs/common.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -434,18 +434,6 @@
434434
# .. toggle_tickets: https://openedx.atlassian.net/browse/DEPR-58
435435
'DEPRECATE_OLD_COURSE_KEYS_IN_STUDIO': True,
436436

437-
# .. toggle_name: FEATURES['ENABLE_LIBRARY_AUTHORING_MICROFRONTEND']
438-
# .. toggle_implementation: DjangoSetting
439-
# .. toggle_default: False
440-
# .. toggle_description: Set to True to enable the Library Authoring MFE
441-
# .. toggle_use_cases: temporary
442-
# .. toggle_creation_date: 2020-06-20
443-
# .. toggle_target_removal_date: 2020-12-31
444-
# .. toggle_tickets: https://openedx.atlassian.net/wiki/spaces/OEPM/pages/4106944527/Libraries+Relaunch+Proposal+For+Product+Review
445-
# .. toggle_warning: Also set settings.LIBRARY_AUTHORING_MICROFRONTEND_URL and see
446-
# REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND for rollout.
447-
'ENABLE_LIBRARY_AUTHORING_MICROFRONTEND': False,
448-
449437
# .. toggle_name: FEATURES['DISABLE_COURSE_CREATION']
450438
# .. toggle_implementation: DjangoSetting
451439
# .. toggle_default: False
@@ -601,7 +589,6 @@
601589
COURSE_AUTHORING_MICROFRONTEND_URL = None
602590
DISCUSSIONS_MICROFRONTEND_URL = None
603591
DISCUSSIONS_MFE_FEEDBACK_URL = None
604-
LIBRARY_AUTHORING_MICROFRONTEND_URL = None
605592
# .. toggle_name: ENABLE_AUTHN_RESET_PASSWORD_HIBP_POLICY
606593
# .. toggle_implementation: DjangoSetting
607594
# .. toggle_default: False
@@ -2779,6 +2766,7 @@
27792766
CUSTOM_PAGES_HELP_URL = "https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/pages.html#adding-custom-pages"
27802767
COURSE_LIVE_HELP_URL = "https://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/course_assets/course_live.html"
27812768
ORA_SETTINGS_HELP_URL = "https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/course_assets/pages.html#configuring-course-level-open-response-assessment-settings"
2769+
# pylint: enable=line-too-long
27822770

27832771
# keys for big blue button live provider
27842772
COURSE_LIVE_GLOBAL_CREDENTIALS = {}
@@ -2810,6 +2798,7 @@
28102798
# Learn More link in upgraded discussion notification alert
28112799
# pylint: disable=line-too-long
28122800
DISCUSSIONS_INCONTEXT_LEARNMORE_URL = "https://edx.readthedocs.io/projects/open-edx-building-and-running-a-course/en/latest/manage_discussions/discussions.html"
2801+
# pylint: enable=line-too-long
28132802

28142803
#### django-simple-history##
28152804
# disable indexing on date field its coming django-simple-history.

cms/envs/devstack.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,6 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing
174174
################### FRONTEND APPLICATION PUBLISHER URL ###################
175175
FEATURES['FRONTEND_APP_PUBLISHER_URL'] = 'http://localhost:18400'
176176

177-
################### FRONTEND APPLICATION LIBRARY AUTHORING ###################
178-
LIBRARY_AUTHORING_MICROFRONTEND_URL = 'http://localhost:3001'
179-
180177
################### FRONTEND APPLICATION COURSE AUTHORING ###################
181178
COURSE_AUTHORING_MICROFRONTEND_URL = 'http://localhost:2001'
182179

cms/templates/index.html

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,17 +348,13 @@ <h3 class="course-title">${course_info['display_name']}</h3>
348348
% endif
349349

350350
% if libraries_enabled:
351-
% if redirect_to_library_authoring_mfe:
352-
<li><a href="${library_authoring_mfe_url}">${_("Libraries")}</a></li>
353-
%else:
354351
<li class="libraries-tab ${ 'active' if active_tab == 'libraries' else ''}">
355352
% if split_studio_home:
356353
<a href="${reverse('home_library')}">${_("Libraries")}</a>
357354
% else:
358355
<a href="#" >${_("Libraries")}</a>
359356
% endif
360357
</li>
361-
% endif
362358
% endif
363359
% if taxonomies_enabled:
364360
<li><a href="${taxonomy_list_mfe_url}">${_("Taxonomies")}</li>

docs/docs_settings.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
ADVANCED_PROBLEM_TYPES,
1515
COURSE_IMPORT_EXPORT_STORAGE,
1616
GIT_EXPORT_DEFAULT_IDENT,
17-
LIBRARY_AUTHORING_MICROFRONTEND_URL,
1817
SCRAPE_YOUTUBE_THUMBNAILS_JOB_QUEUE,
1918
VIDEO_TRANSCRIPT_MIGRATIONS_JOB_QUEUE,
2019
UPDATE_SEARCH_INDEX_JOB_QUEUE,

lms/djangoapps/course_blocks/transformers/library_content.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
BlockStructureTransformer,
1515
FilteringTransformerMixin
1616
)
17-
from xmodule.library_content_block import LibraryContentBlock # lint-amnesty, pylint: disable=wrong-import-order
17+
from xmodule.library_content_block import LegacyLibraryContentBlock # lint-amnesty, pylint: disable=wrong-import-order
1818
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
1919

2020
from ..utils import get_student_module_as_dict
@@ -47,7 +47,6 @@ def collect(cls, block_structure):
4747
Collects any information that's necessary to execute this
4848
transformer's transform method.
4949
"""
50-
block_structure.request_xblock_fields('mode')
5150
block_structure.request_xblock_fields('max_count')
5251
block_structure.request_xblock_fields('category')
5352
store = modulestore()
@@ -83,7 +82,6 @@ def transform_block_filters(self, usage_info, block_structure):
8382
if library_children:
8483
all_library_children.update(library_children)
8584
selected = []
86-
mode = block_structure.get_xblock_field(block_key, 'mode')
8785
max_count = block_structure.get_xblock_field(block_key, 'max_count')
8886
if max_count < 0:
8987
max_count = len(library_children)
@@ -100,7 +98,7 @@ def transform_block_filters(self, usage_info, block_structure):
10098

10199
# Update selected
102100
previous_count = len(selected)
103-
block_keys = LibraryContentBlock.make_selection(selected, library_children, max_count, mode)
101+
block_keys = LegacyLibraryContentBlock.make_selection(selected, library_children, max_count)
104102
selected = block_keys['selected']
105103

106104
# Save back any changes
@@ -176,7 +174,7 @@ def publish_event(event_name, result, **kwargs):
176174
with tracker.get_tracker().context(full_event_name, context):
177175
tracker.emit(full_event_name, event_data)
178176

179-
LibraryContentBlock.publish_selected_children_events(
177+
LegacyLibraryContentBlock.publish_selected_children_events(
180178
block_keys,
181179
format_block_keys,
182180
publish_event,

lms/djangoapps/courseware/block_render.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from openedx.core.lib.xblock_services.call_to_action import CallToActionService
4646
from xmodule.contentstore.django import contentstore
4747
from xmodule.exceptions import NotFoundError, ProcessingError
48-
from xmodule.library_tools import LibraryToolsService
48+
from xmodule.library_tools import LegacyLibraryToolsService
4949
from xmodule.modulestore.django import XBlockI18nService, modulestore
5050
from xmodule.modulestore.exceptions import ItemNotFoundError
5151
from xmodule.partitions.partitions_service import PartitionService
@@ -626,7 +626,7 @@ def inner_get_block(block: XBlock) -> XBlock | None:
626626
),
627627
'completion': CompletionService(user=user, context_key=course_id) if user and user.is_authenticated else None,
628628
'i18n': XBlockI18nService,
629-
'library_tools': LibraryToolsService(store, user_id=user.id if user else None),
629+
'library_tools': LegacyLibraryToolsService(store, user_id=user.id if user else None),
630630
'partitions': PartitionService(course_id=course_id, cache=DEFAULT_REQUEST_CACHE.data),
631631
'settings': SettingsService(),
632632
'user_tags': UserTagsService(user=user, course_id=course_id),

0 commit comments

Comments
 (0)