Skip to content

Commit 77cc7a3

Browse files
committed
chore: move video sharing methods to video_config app
1 parent 9110ae0 commit 77cc7a3

File tree

11 files changed

+195
-83
lines changed

11 files changed

+195
-83
lines changed

.github/workflows/unit-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ jobs:
1717
name: ${{ matrix.shard_name }}(py=${{ matrix.python-version }},dj=${{ matrix.django-version }},mongo=${{ matrix.mongo-version }})
1818
runs-on: ${{ matrix.os-version }}
1919
strategy:
20+
fail-fast: false
2021
matrix:
2122
python-version:
2223
- "3.11"

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.utils 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.utils import VideoSharingUtils
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
@@ -261,14 +262,14 @@ def test_is_public_sharing_enabled(self, feature_enabled):
261262
"""Test public video url."""
262263
assert self.block.public_access is True
263264
with self.mock_feature_toggle(enabled=feature_enabled):
264-
assert self.block.is_public_sharing_enabled() == feature_enabled
265+
assert VideoSharingUtils.is_public_sharing_enabled(self.block) == feature_enabled
265266

266267
def test_is_public_sharing_enabled__not_public(self):
267268
self.block.public_access = False
268269
with self.mock_feature_toggle():
269-
assert not self.block.is_public_sharing_enabled()
270+
assert not VideoSharingUtils.is_public_sharing_enabled(self.block)
270271

271-
@patch('xmodule.video_block.video_block.VideoBlock.get_course_video_sharing_override')
272+
@patch('openedx.core.djangoapps.video_config.utils.VideoSharingUtils.get_course_video_sharing_override')
272273
def test_is_public_sharing_enabled_by_course_override(self, mock_course_sharing_override):
273274

274275
# Given a course overrides all videos to be shared
@@ -277,47 +278,47 @@ def test_is_public_sharing_enabled_by_course_override(self, mock_course_sharing_
277278

278279
# When I try to determine if public sharing is enabled
279280
with self.mock_feature_toggle():
280-
is_public_sharing_enabled = self.block.is_public_sharing_enabled()
281+
is_public_sharing_enabled = VideoSharingUtils.is_public_sharing_enabled(self.block)
281282

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

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

291292
# When I try to determine if public sharing is enabled
292293
with self.mock_feature_toggle():
293-
is_public_sharing_enabled = self.block.is_public_sharing_enabled()
294+
is_public_sharing_enabled = VideoSharingUtils.is_public_sharing_enabled(self.block)
294295

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

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

305306
# When I try to determine if public sharing is enabled
306307
with self.mock_feature_toggle():
307-
is_public_sharing_enabled = self.block.is_public_sharing_enabled()
308+
is_public_sharing_enabled = VideoSharingUtils.is_public_sharing_enabled(self.block)
308309

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

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

318319
# When I try to determine if public sharing is enabled
319320
with self.mock_feature_toggle():
320-
is_public_sharing_enabled = self.block.is_public_sharing_enabled()
321+
is_public_sharing_enabled = VideoSharingUtils.is_public_sharing_enabled(self.block)
321322

322323
# I will fall-back to per-video values
323324
self.assertEqual(self.block.public_access, is_public_sharing_enabled)
@@ -326,7 +327,7 @@ def test_is_public_sharing_course_not_found(self, mock_get_course):
326327
def test_context(self, is_public_sharing_enabled):
327328
with self.mock_feature_toggle():
328329
with patch.object(
329-
self.block,
330+
VideoSharingUtils,
330331
'is_public_sharing_enabled',
331332
return_value=is_public_sharing_enabled
332333
):

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.utils import VideoSharingUtils
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 VideoSharingUtils.is_public_sharing_enabled(video_block):
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.utils import VideoSharingUtils
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 VideoSharingUtils.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_id: 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 VideoSharingUtils.is_public_sharing_enabled(video_block):
49+
return context
50+
51+
public_video_url = VideoSharingUtils.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 xmodule.video_block.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: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""
2+
Video configuration utilities
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+
class VideoSharingUtils:
22+
"""
23+
Provides utility methods for video sharing functionality.
24+
"""
25+
26+
@staticmethod
27+
def get_public_video_url(usage_id: UsageKey) -> str:
28+
"""
29+
Returns the public video url
30+
"""
31+
return fr'{settings.LMS_ROOT_URL}/videos/{str(usage_id)}'
32+
33+
@staticmethod
34+
def is_public_sharing_enabled(video_block):
35+
"""
36+
Check if public sharing is enabled for a video.
37+
38+
Args:
39+
video_block: The video XBlock instance
40+
41+
Returns:
42+
bool: True if public sharing is enabled, False otherwise
43+
"""
44+
if not video_block.context_key.is_course:
45+
return False # Only courses support this feature (not libraries)
46+
47+
try:
48+
# Video share feature must be enabled for sharing settings to take effect
49+
feature_enabled = PUBLIC_VIDEO_SHARE.is_enabled(video_block.context_key)
50+
except Exception as err: # pylint: disable=broad-except
51+
log.exception(f"Error retrieving course for course ID: {video_block.context_key}")
52+
return False
53+
54+
if not feature_enabled:
55+
return False
56+
57+
# Check if the course specifies a general setting
58+
course_video_sharing_option = VideoSharingUtils.get_course_video_sharing_override(video_block)
59+
60+
# Course can override all videos to be shared
61+
if course_video_sharing_option == COURSE_VIDEO_SHARING_ALL_VIDEOS:
62+
return True
63+
64+
# ... or no videos to be shared
65+
elif course_video_sharing_option == COURSE_VIDEO_SHARING_NONE:
66+
return False
67+
68+
# ... or can fall back to per-video setting
69+
# Equivalent to COURSE_VIDEO_SHARING_PER_VIDEO or None / unset
70+
else:
71+
return video_block.public_access
72+
73+
@staticmethod
74+
def get_course_video_sharing_override(video_block):
75+
"""
76+
Return course video sharing options override or None
77+
78+
Args:
79+
video_block: The video XBlock instance
80+
81+
Returns:
82+
Course video sharing option or None
83+
"""
84+
if not video_block.context_key.is_course:
85+
return False # Only courses support this feature (not libraries)
86+
87+
try:
88+
course = get_course_by_id(video_block.context_key)
89+
return getattr(course, 'video_sharing_options', None)
90+
except Exception as err: # pylint: disable=broad-except
91+
log.exception(f"Error retrieving course for course ID: {video_block.course_id}")
92+
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.utils 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/modulestore/split_mongo/caching_descriptor_system.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from xmodule.modulestore.split_mongo.split_mongo_kvs import SplitMongoKVS
2626
from xmodule.util.misc import get_library_or_course_attribute
2727
from xmodule.x_module import XModuleMixin
28+
from openedx.core.djangoapps.video_config.services import VideoConfigService
2829

2930
log = logging.getLogger(__name__)
3031

@@ -242,6 +243,8 @@ def service(self, block, service_name):
242243
self.block_field_datas[block] = None
243244
raise
244245
return self.block_field_datas[block]
246+
elif service_name == 'video_config':
247+
return VideoConfigService()
245248
return super().service(block, service_name)
246249

247250
def _init_field_data_for_block(self, block):

xmodule/tests/__init__.py

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

3738

@@ -242,14 +243,15 @@ def get_test_descriptor_system(render_template=None, **kwargs):
242243
Construct a test DescriptorSystem instance.
243244
"""
244245
field_data = DictFieldData({})
246+
video_config = VideoConfigService()
245247

246248
descriptor_system = TestDescriptorSystem(
247249
load_item=Mock(name='get_test_descriptor_system.load_item'),
248250
resources_fs=Mock(name='get_test_descriptor_system.resources_fs'),
249251
error_tracker=Mock(name='get_test_descriptor_system.error_tracker'),
250252
render_template=render_template or mock_render_template,
251253
mixins=(InheritanceMixin, XModuleMixin),
252-
services={'field-data': field_data},
254+
services={'field-data': field_data, 'video_config': video_config},
253255
**kwargs
254256
)
255257
descriptor_system.get_asides = lambda block: []

0 commit comments

Comments
 (0)