Skip to content

Commit 0cd62bf

Browse files
committed
refactor: Introduce VideoConfig service, move video sharing methods in it
1 parent bbd53a0 commit 0cd62bf

9 files changed

Lines changed: 182 additions & 83 deletions

File tree

cms/djangoapps/contentstore/views/preview.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from xblock.exceptions import NoSuchHandlerError
1919
from xblock.runtime import KvsFieldData
2020

21+
from openedx.core.djangoapps.video_config.services import VideoConfigService
2122
from xmodule.contentstore.django import contentstore
2223
from xmodule.exceptions import NotFoundError, ProcessingError
2324
from xmodule.modulestore.django import XBlockI18nService, modulestore
@@ -214,7 +215,8 @@ def _prepare_runtime_for_preview(request, block):
214215
"teams_configuration": TeamsConfigurationService(),
215216
"sandbox": SandboxService(contentstore=contentstore, course_id=course_id),
216217
"cache": CacheService(cache),
217-
'replace_urls': ReplaceURLService
218+
'replace_urls': ReplaceURLService,
219+
'video_config': VideoConfigService(),
218220
}
219221

220222
block.runtime.get_block_for_descriptor = partial(_load_preview_block, request)

lms/djangoapps/courseware/block_render.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
from xblock.runtime import KvsFieldData
4343

4444
from lms.djangoapps.teams.services import TeamsService
45+
from openedx.core.djangoapps.video_config.services import VideoConfigService
4546
from openedx.core.lib.xblock_services.call_to_action import CallToActionService
4647
from xmodule.contentstore.django import contentstore
4748
from xmodule.exceptions import NotFoundError, ProcessingError
@@ -635,6 +636,7 @@ def inner_get_block(block: XBlock) -> XBlock | None:
635636
'call_to_action': CallToActionService(),
636637
'publish': EventPublishingService(user, course_id, track_function),
637638
'enrollments': EnrollmentsService(),
639+
'video_config': VideoConfigService(),
638640
}
639641

640642
runtime.get_block_for_descriptor = inner_get_block

lms/djangoapps/courseware/tests/test_video_mongo.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
from lxml import etree
3838
from path import Path as path
3939
from xmodule.contentstore.content import StaticContent
40-
from xmodule.course_block import (
40+
from openedx.core.djangoapps.video_config.sharing import (
4141
COURSE_VIDEO_SHARING_ALL_VIDEOS,
4242
COURSE_VIDEO_SHARING_NONE,
4343
COURSE_VIDEO_SHARING_PER_VIDEO
@@ -57,6 +57,7 @@
5757
from common.djangoapps.xblock_django.constants import ATTR_KEY_REQUEST_COUNTRY_CODE
5858
from lms.djangoapps.courseware.tests.helpers import get_context_dict_from_string
5959
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
60+
from openedx.core.djangoapps.video_config import sharing
6061
from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTUBE
6162
from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel
6263
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
@@ -260,14 +261,14 @@ def test_is_public_sharing_enabled(self, feature_enabled):
260261
"""Test public video url."""
261262
assert self.block.public_access is True
262263
with self.mock_feature_toggle(enabled=feature_enabled):
263-
assert self.block.is_public_sharing_enabled() == feature_enabled
264+
assert sharing.is_public_sharing_enabled(self.block.location, self.block.public_access) == feature_enabled
264265

265266
def test_is_public_sharing_enabled__not_public(self):
266267
self.block.public_access = False
267268
with self.mock_feature_toggle():
268-
assert not self.block.is_public_sharing_enabled()
269+
assert not sharing.is_public_sharing_enabled(self.block.location, self.block.public_access)
269270

270-
@patch('xmodule.video_block.video_block.VideoBlock.get_course_video_sharing_override')
271+
@patch('openedx.core.djangoapps.video_config.sharing.get_course_video_sharing_override')
271272
def test_is_public_sharing_enabled_by_course_override(self, mock_course_sharing_override):
272273

273274
# Given a course overrides all videos to be shared
@@ -276,47 +277,47 @@ def test_is_public_sharing_enabled_by_course_override(self, mock_course_sharing_
276277

277278
# When I try to determine if public sharing is enabled
278279
with self.mock_feature_toggle():
279-
is_public_sharing_enabled = self.block.is_public_sharing_enabled()
280+
is_public_sharing_enabled = sharing.is_public_sharing_enabled(self.block.location, self.block.public_access)
280281

281282
# Then I will get that course value
282283
self.assertTrue(is_public_sharing_enabled)
283284

284-
@patch('xmodule.video_block.video_block.VideoBlock.get_course_video_sharing_override')
285+
@patch('openedx.core.djangoapps.video_config.sharing.get_course_video_sharing_override')
285286
def test_is_public_sharing_disabled_by_course_override(self, mock_course_sharing_override):
286287
# Given a course overrides no videos to be shared
287288
mock_course_sharing_override.return_value = COURSE_VIDEO_SHARING_NONE
288289
self.block.public_access = 'some-arbitrary-value'
289290

290291
# When I try to determine if public sharing is enabled
291292
with self.mock_feature_toggle():
292-
is_public_sharing_enabled = self.block.is_public_sharing_enabled()
293+
is_public_sharing_enabled = sharing.is_public_sharing_enabled(self.block.location, self.block.public_access)
293294

294295
# Then I will get that course value
295296
self.assertFalse(is_public_sharing_enabled)
296297

297298
@ddt.data(COURSE_VIDEO_SHARING_PER_VIDEO, None)
298-
@patch('xmodule.video_block.video_block.VideoBlock.get_course_video_sharing_override')
299+
@patch('openedx.core.djangoapps.video_config.sharing.get_course_video_sharing_override')
299300
def test_is_public_sharing_enabled_per_video(self, mock_override_value, mock_course_sharing_override):
300301
# Given a course does not override per-video settings
301302
mock_course_sharing_override.return_value = mock_override_value
302303
self.block.public_access = 'some-arbitrary-value'
303304

304305
# When I try to determine if public sharing is enabled
305306
with self.mock_feature_toggle():
306-
is_public_sharing_enabled = self.block.is_public_sharing_enabled()
307+
is_public_sharing_enabled = sharing.is_public_sharing_enabled(self.block.location, self.block.public_access)
307308

308309
# I will get the per-video value
309310
self.assertEqual(self.block.public_access, is_public_sharing_enabled)
310311

311-
@patch('xmodule.video_block.video_block.get_course_by_id')
312+
@patch('openedx.core.lib.courses.get_course_by_id')
312313
def test_is_public_sharing_course_not_found(self, mock_get_course):
313314
# Given a course does not override per-video settings
314315
mock_get_course.side_effect = Http404()
315316
self.block.public_access = 'some-arbitrary-value'
316317

317318
# When I try to determine if public sharing is enabled
318319
with self.mock_feature_toggle():
319-
is_public_sharing_enabled = self.block.is_public_sharing_enabled()
320+
is_public_sharing_enabled = sharing.is_public_sharing_enabled(self.block.location, self.block.public_access)
320321

321322
# I will fall-back to per-video values
322323
self.assertEqual(self.block.public_access, is_public_sharing_enabled)
@@ -325,7 +326,7 @@ def test_is_public_sharing_course_not_found(self, mock_get_course):
325326
def test_context(self, is_public_sharing_enabled):
326327
with self.mock_feature_toggle():
327328
with patch.object(
328-
self.block,
329+
sharing,
329330
'is_public_sharing_enabled',
330331
return_value=is_public_sharing_enabled
331332
):

lms/djangoapps/courseware/views/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
139139
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
140140
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
141+
from openedx.core.djangoapps.video_config.sharing import is_public_sharing_enabled
141142
from openedx.core.djangoapps.zendesk_proxy.utils import create_zendesk_ticket
142143
from openedx.core.djangolib.markup import HTML, Text
143144
from openedx.core.lib.courses import get_course_by_id
@@ -1869,7 +1870,7 @@ def get_course_and_video_block(self, usage_key_string):
18691870
)
18701871

18711872
# Block must be marked as public to be viewed
1872-
if not video_block.is_public_sharing_enabled():
1873+
if not is_public_sharing_enabled(video_block.location, video_block.public_access):
18731874
raise Http404("Video not found.")
18741875

18751876
return course, video_block
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""
2+
Video Configuration Service for XBlock runtime.
3+
4+
This service provides video-related configuration and feature flags
5+
that are specific to the edx-platform implementation
6+
for the extracted video block in xblocks-contrib repository.
7+
"""
8+
9+
import logging
10+
11+
from opaque_keys.edx.keys import CourseKey, UsageKey
12+
13+
from openedx.core.djangoapps.video_config import sharing
14+
from organizations.api import get_course_organization
15+
16+
17+
log = logging.getLogger(__name__)
18+
19+
20+
class VideoConfigService:
21+
"""
22+
Service for providing video-related configuration and feature flags.
23+
24+
This service abstracts away edx-platform specific functionality
25+
that the Video XBlock needs, allowing the Video XBlock to be
26+
extracted to a separate repository.
27+
"""
28+
29+
def get_public_video_url(self, usage_id: UsageKey) -> str:
30+
"""
31+
Returns the public video url
32+
"""
33+
return sharing.get_public_video_url(usage_id)
34+
35+
def get_public_sharing_context(self, video_block, course_key: CourseKey) -> dict:
36+
"""
37+
Get the complete public sharing context for a video.
38+
39+
Args:
40+
video_block: The video XBlock instance
41+
course_key: The course identifier
42+
43+
Returns:
44+
dict: Context dictionary with sharing information, empty if sharing is disabled
45+
"""
46+
context = {}
47+
48+
if not sharing.is_public_sharing_enabled(video_block.location, video_block.public_access):
49+
return context
50+
51+
public_video_url = sharing.get_public_video_url(video_block.location)
52+
context['public_sharing_enabled'] = True
53+
context['public_video_url'] = public_video_url
54+
55+
organization = get_course_organization(course_key)
56+
57+
from openedx.core.djangoapps.video_config.sharing_sites import sharing_sites_info_for_video
58+
sharing_sites_info = sharing_sites_info_for_video(
59+
public_video_url,
60+
organization=organization
61+
)
62+
context['sharing_sites_info'] = sharing_sites_info
63+
64+
return context
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""
2+
Provides utility methods for video sharing functionality.
3+
"""
4+
5+
import logging
6+
7+
from django.conf import settings
8+
from opaque_keys.edx.keys import UsageKey
9+
10+
from openedx.core.djangoapps.video_config.toggles import PUBLIC_VIDEO_SHARE
11+
from openedx.core.lib.courses import get_course_by_id
12+
13+
log = logging.getLogger(__name__)
14+
15+
# Video sharing constants
16+
COURSE_VIDEO_SHARING_PER_VIDEO = 'per-video'
17+
COURSE_VIDEO_SHARING_ALL_VIDEOS = 'all-on'
18+
COURSE_VIDEO_SHARING_NONE = 'all-off'
19+
20+
21+
@staticmethod
22+
def get_public_video_url(usage_id: UsageKey) -> str:
23+
"""
24+
Returns the public video url
25+
"""
26+
return fr'{settings.LMS_ROOT_URL}/videos/{str(usage_id)}'
27+
28+
29+
@staticmethod
30+
def is_public_sharing_enabled(usage_key: UsageKey, public_access: bool) -> bool:
31+
"""
32+
Check if public sharing is enabled for a video.
33+
34+
Args:
35+
usage_key: The usage key of the video block
36+
public_access: Whether the video block has public access enabled
37+
"""
38+
if not usage_key.context_key.is_course:
39+
return False # Only courses support this feature (not libraries)
40+
41+
try:
42+
# Video share feature must be enabled for sharing settings to take effect
43+
feature_enabled = PUBLIC_VIDEO_SHARE.is_enabled(usage_key.context_key)
44+
except Exception as err: # pylint: disable=broad-except
45+
log.exception(f"Error retrieving course for course ID: {usage_key.context_key}")
46+
return False
47+
48+
if not feature_enabled:
49+
return False
50+
51+
# Check if the course specifies a general setting
52+
course_video_sharing_option = get_course_video_sharing_override(usage_key)
53+
54+
# Course can override all videos to be shared
55+
if course_video_sharing_option == COURSE_VIDEO_SHARING_ALL_VIDEOS:
56+
return True
57+
58+
# ... or no videos to be shared
59+
elif course_video_sharing_option == COURSE_VIDEO_SHARING_NONE:
60+
return False
61+
62+
# ... or can fall back to per-video setting
63+
# Equivalent to COURSE_VIDEO_SHARING_PER_VIDEO or None / unset
64+
else:
65+
return public_access
66+
67+
68+
@staticmethod
69+
def get_course_video_sharing_override(usage_key: UsageKey) -> str | None:
70+
"""
71+
Return course video sharing options override
72+
"""
73+
if not usage_key.context_key.is_course:
74+
return False # Only courses support this feature (not libraries)
75+
76+
try:
77+
course = get_course_by_id(usage_key.context_key)
78+
return getattr(course, 'video_sharing_options', None)
79+
except Exception as err: # pylint: disable=broad-except
80+
log.exception(f"Error retrieving course for course ID: {usage_key.context_key}")
81+
return None

xmodule/course_block.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
from pytz import utc
1919
from xblock.fields import Boolean, Dict, Float, Integer, List, Scope, String
2020
from openedx.core.djangoapps.video_pipeline.models import VideoUploadsEnabledByDefault
21+
from openedx.core.djangoapps.video_config.sharing import (
22+
COURSE_VIDEO_SHARING_ALL_VIDEOS,
23+
COURSE_VIDEO_SHARING_NONE,
24+
COURSE_VIDEO_SHARING_PER_VIDEO,
25+
)
2126
from openedx.core.lib.license import LicenseMixin
2227
from openedx.core.lib.teams_config import TeamsConfig # lint-amnesty, pylint: disable=unused-import
2328
from xmodule import course_metadata_utils
@@ -55,9 +60,6 @@
5560
COURSE_VISIBILITY_PUBLIC_OUTLINE = 'public_outline'
5661
COURSE_VISIBILITY_PUBLIC = 'public'
5762

58-
COURSE_VIDEO_SHARING_PER_VIDEO = 'per-video'
59-
COURSE_VIDEO_SHARING_ALL_VIDEOS = 'all-on'
60-
COURSE_VIDEO_SHARING_NONE = 'all-off'
6163
# .. toggle_name: FEATURES['CREATE_COURSE_WITH_DEFAULT_ENROLLMENT_START_DATE']
6264
# .. toggle_implementation: SettingDictToggle
6365
# .. toggle_default: False

xmodule/tests/__init__.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from xmodule.tests.helpers import StubReplaceURLService, mock_render_template, StubMakoService, StubUserService
3232
from xmodule.util.sandboxing import SandboxService
3333
from xmodule.x_module import DoNothingCache, XModuleMixin, ModuleStoreRuntime
34+
from openedx.core.djangoapps.video_config.services import VideoConfigService
3435
from openedx.core.lib.cache_utils import CacheService
3536

3637

@@ -159,6 +160,7 @@ def get_block(block):
159160
'cache': CacheService(DoNothingCache()),
160161
'field-data': DictFieldData({}),
161162
'sandbox': SandboxService(contentstore, course_id),
163+
'video_config': VideoConfigService(),
162164
}
163165

164166
descriptor_system.get_block_for_descriptor = get_block # lint-amnesty, pylint: disable=attribute-defined-outside-init
@@ -214,6 +216,7 @@ def get_block(block):
214216
'cache': CacheService(DoNothingCache()),
215217
'field-data': DictFieldData({}),
216218
'sandbox': SandboxService(contentstore, course_id),
219+
'video_config': VideoConfigService(),
217220
}
218221

219222
if add_overrides:
@@ -241,14 +244,15 @@ def get_test_descriptor_system(render_template=None, **kwargs):
241244
Construct a test ModuleStoreRuntime instance.
242245
"""
243246
field_data = DictFieldData({})
247+
video_config = VideoConfigService()
244248

245249
descriptor_system = TestModuleStoreRuntime(
246250
load_item=Mock(name='get_test_descriptor_system.load_item'),
247251
resources_fs=Mock(name='get_test_descriptor_system.resources_fs'),
248252
error_tracker=Mock(name='get_test_descriptor_system.error_tracker'),
249253
render_template=render_template or mock_render_template,
250254
mixins=(InheritanceMixin, XModuleMixin),
251-
services={'field-data': field_data},
255+
services={'field-data': field_data, 'video_config': video_config},
252256
**kwargs
253257
)
254258
descriptor_system.get_asides = lambda block: []

0 commit comments

Comments
 (0)