Skip to content

Commit 8aebf6a

Browse files
committed
chore: original code.
This commit contains original code copied from edx-platform. Purpose of keeping the first commit with original code is to get help in the review process. Reviewer just needs to open the second commit and code changes will be clearly visible.
1 parent 68d85ea commit 8aebf6a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+13578
-124
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# NOTE: Code has been copied from the following source file
2+
# https://github.com/openedx/edx-platform/blob/master/xmodule/x_module.py#L739
3+
4+
class XModuleToXBlockMixin:
5+
"""
6+
Common code needed by XModule and XBlocks converted from XModules.
7+
"""
8+
@property
9+
def ajax_url(self):
10+
"""
11+
Returns the URL for the ajax handler.
12+
"""
13+
return self.runtime.handler_url(self, 'xmodule_handler', '', '').rstrip('/?')
14+
15+
@XBlock.handler
16+
def xmodule_handler(self, request, suffix=None):
17+
"""
18+
XBlock handler that wraps `handle_ajax`
19+
"""
20+
class FileObjForWebobFiles:
21+
"""
22+
Turn Webob cgi.FieldStorage uploaded files into pure file objects.
23+
24+
Webob represents uploaded files as cgi.FieldStorage objects, which
25+
have a .file attribute. We wrap the FieldStorage object, delegating
26+
attribute access to the .file attribute. But the files have no
27+
name, so we carry the FieldStorage .filename attribute as the .name.
28+
29+
"""
30+
def __init__(self, webob_file):
31+
self.file = webob_file.file
32+
self.name = webob_file.filename
33+
34+
def __getattr__(self, name):
35+
return getattr(self.file, name)
36+
37+
# WebOb requests have multiple entries for uploaded files. handle_ajax
38+
# expects a single entry as a list.
39+
request_post = MultiDict(request.POST)
40+
for key in set(request.POST.keys()):
41+
if hasattr(request.POST[key], "file"):
42+
request_post[key] = list(map(FileObjForWebobFiles, request.POST.getall(key)))
43+
44+
response_data = self.handle_ajax(suffix, request_post)
45+
return Response(response_data, content_type='application/json', charset='UTF-8')
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# NOTE: Code has been copied from the following source files
2+
# https://github.com/openedx/edx-platform/blob/master/xmodule/video_block/bumper_utils.py
3+
"""
4+
Utils for video bumper
5+
"""
6+
7+
8+
import copy
9+
import json
10+
import logging
11+
from collections import OrderedDict
12+
from datetime import datetime, timedelta
13+
14+
import pytz
15+
from django.conf import settings
16+
17+
from .video_utils import set_query_parameter
18+
19+
try:
20+
import edxval.api as edxval_api
21+
except ImportError:
22+
edxval_api = None
23+
24+
log = logging.getLogger(__name__)
25+
26+
27+
def get_bumper_settings(video):
28+
"""
29+
Get bumper settings from video instance.
30+
"""
31+
bumper_settings = copy.deepcopy(getattr(video, 'video_bumper', {}))
32+
33+
# clean up /static/ prefix from bumper transcripts
34+
for lang, transcript_url in bumper_settings.get('transcripts', {}).items():
35+
bumper_settings['transcripts'][lang] = transcript_url.replace("/static/", "")
36+
37+
return bumper_settings
38+
39+
40+
def is_bumper_enabled(video):
41+
"""
42+
Check if bumper enabled.
43+
44+
- Feature flag ENABLE_VIDEO_BUMPER should be set to True
45+
- Do not show again button should not be clicked by user.
46+
- Current time minus periodicity must be greater that last time viewed
47+
- edxval_api should be presented
48+
49+
Returns:
50+
bool.
51+
"""
52+
bumper_last_view_date = getattr(video, 'bumper_last_view_date', None)
53+
utc_now = datetime.utcnow().replace(tzinfo=pytz.utc)
54+
periodicity = settings.FEATURES.get('SHOW_BUMPER_PERIODICITY', 0)
55+
has_viewed = any([
56+
video.bumper_do_not_show_again,
57+
(bumper_last_view_date and bumper_last_view_date + timedelta(seconds=periodicity) > utc_now)
58+
])
59+
is_studio = getattr(video.runtime, "is_author_mode", False)
60+
return bool(
61+
not is_studio and
62+
settings.FEATURES.get('ENABLE_VIDEO_BUMPER') and
63+
get_bumper_settings(video) and
64+
edxval_api and
65+
not has_viewed
66+
)
67+
68+
69+
def bumperize(video):
70+
"""
71+
Populate video with bumper settings, if they are presented.
72+
"""
73+
video.bumper = {
74+
'enabled': False,
75+
'edx_video_id': "",
76+
'transcripts': {},
77+
'metadata': None,
78+
}
79+
80+
if not is_bumper_enabled(video):
81+
return
82+
83+
bumper_settings = get_bumper_settings(video)
84+
85+
try:
86+
video.bumper['edx_video_id'] = bumper_settings['video_id']
87+
video.bumper['transcripts'] = bumper_settings['transcripts']
88+
except (TypeError, KeyError):
89+
log.warning(
90+
"Could not retrieve video bumper information from course settings"
91+
)
92+
return
93+
94+
sources = get_bumper_sources(video)
95+
if not sources:
96+
return
97+
98+
video.bumper.update({
99+
'metadata': bumper_metadata(video, sources),
100+
'enabled': True, # Video poster needs this.
101+
})
102+
103+
104+
def get_bumper_sources(video):
105+
"""
106+
Get bumper sources from edxval.
107+
108+
Returns list of sources.
109+
"""
110+
try:
111+
val_profiles = ["desktop_webm", "desktop_mp4"]
112+
val_video_urls = edxval_api.get_urls_for_profiles(video.bumper['edx_video_id'], val_profiles)
113+
bumper_sources = [url for url in [val_video_urls[p] for p in val_profiles] if url]
114+
except edxval_api.ValInternalError:
115+
# if no bumper sources, nothing will be showed
116+
log.warning(
117+
"Could not retrieve information from VAL for Bumper edx Video ID: %s.", video.bumper['edx_video_id']
118+
)
119+
return []
120+
121+
return bumper_sources
122+
123+
124+
def bumper_metadata(video, sources):
125+
"""
126+
Generate bumper metadata.
127+
"""
128+
transcripts = video.get_transcripts_info(is_bumper=True)
129+
unused_track_url, bumper_transcript_language, bumper_languages = video.get_transcripts_for_student(transcripts)
130+
131+
metadata = OrderedDict({
132+
'saveStateUrl': video.ajax_url + '/save_user_state',
133+
'showCaptions': json.dumps(video.show_captions),
134+
'sources': sources,
135+
'streams': '',
136+
'transcriptLanguage': bumper_transcript_language,
137+
'transcriptLanguages': bumper_languages,
138+
'transcriptTranslationUrl': set_query_parameter(
139+
video.runtime.handler_url(video, 'transcript', 'translation/__lang__').rstrip('/?'), 'is_bumper', 1
140+
),
141+
'transcriptAvailableTranslationsUrl': set_query_parameter(
142+
video.runtime.handler_url(video, 'transcript', 'available_translations').rstrip('/?'), 'is_bumper', 1
143+
),
144+
'publishCompletionUrl': set_query_parameter(
145+
video.runtime.handler_url(video, 'publish_completion', '').rstrip('?'), 'is_bumper', 1
146+
),
147+
})
148+
149+
return metadata
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# NOTE: Code has been copied from the following source files
2+
# https://github.com/openedx/edx-platform/blob/master/openedx/core/lib/cache_utils.py
3+
"""
4+
Utilities related to caching.
5+
"""
6+
7+
8+
import itertools
9+
10+
import wrapt
11+
from django.utils.encoding import force_str
12+
13+
from edx_django_utils.cache import RequestCache
14+
15+
16+
def request_cached(namespace=None, arg_map_function=None, request_cache_getter=None):
17+
"""
18+
A function decorator that automatically handles caching its return value for
19+
the duration of the request. It returns the cached value for subsequent
20+
calls to the same function, with the same parameters, within a given request.
21+
22+
Notes:
23+
- We convert arguments and keyword arguments to their string form to build the cache key. So if you have
24+
args/kwargs that can't be converted to strings, you're gonna have a bad time (don't do it).
25+
- Cache key cardinality depends on the args/kwargs. So if you're caching a function that takes five arguments,
26+
you might have deceptively low cache efficiency. Prefer functions with fewer arguments.
27+
- WATCH OUT: Don't use this decorator for instance methods that take in a "self" argument that changes each
28+
time the method is called. This will result in constant cache misses and not provide the performance benefit
29+
you are looking for. Rather, change your instance method to a class method.
30+
- Benchmark, benchmark, benchmark! If you never measure, how will you know you've improved? or regressed?
31+
32+
Arguments:
33+
namespace (string): An optional namespace to use for the cache. By default, we use the default request cache,
34+
not a namespaced request cache. Since the code automatically creates a unique cache key with the module and
35+
function's name, storing the cached value in the default cache, you won't usually need to specify a
36+
namespace value.
37+
But you can specify a namespace value here if you need to use your own namespaced cache - for example,
38+
if you want to clear out your own cache by calling RequestCache(namespace=NAMESPACE).clear().
39+
NOTE: This argument is ignored if you supply a ``request_cache_getter``.
40+
arg_map_function (function: arg->string): Function to use for mapping the wrapped function's arguments to
41+
strings to use in the cache key. If not provided, defaults to force_text, which converts the given
42+
argument to a string.
43+
request_cache_getter (function: args, kwargs->RequestCache): Function that returns the RequestCache to use.
44+
If not provided, defaults to edx_django_utils.cache.RequestCache. If ``request_cache_getter`` returns None,
45+
the function's return values are not cached.
46+
47+
Returns:
48+
func: a wrapper function which will call the wrapped function, passing in the same args/kwargs,
49+
cache the value it returns, and return that cached value for subsequent calls with the
50+
same args/kwargs within a single request.
51+
"""
52+
@wrapt.decorator
53+
def decorator(wrapped, instance, args, kwargs):
54+
"""
55+
Arguments:
56+
args, kwargs: values passed into the wrapped function
57+
"""
58+
# Check to see if we have a result in cache. If not, invoke our wrapped
59+
# function. Cache and return the result to the caller.
60+
if request_cache_getter:
61+
request_cache = request_cache_getter(args if instance is None else (instance,) + args, kwargs)
62+
else:
63+
request_cache = RequestCache(namespace)
64+
65+
if request_cache:
66+
cache_key = _func_call_cache_key(wrapped, arg_map_function, *args, **kwargs)
67+
cached_response = request_cache.get_cached_response(cache_key)
68+
if cached_response.is_found:
69+
return cached_response.value
70+
71+
result = wrapped(*args, **kwargs)
72+
73+
if request_cache:
74+
request_cache.set(cache_key, result)
75+
76+
return result
77+
78+
return decorator
79+
80+
81+
def _func_call_cache_key(func, arg_map_function, *args, **kwargs):
82+
"""
83+
Returns a cache key based on the function's module,
84+
the function's name, a stringified list of arguments
85+
and a stringified list of keyword arguments.
86+
"""
87+
arg_map_function = arg_map_function or force_str
88+
89+
converted_args = list(map(arg_map_function, args))
90+
converted_kwargs = list(map(arg_map_function, _sorted_kwargs_list(kwargs)))
91+
92+
cache_keys = [func.__module__, func.__name__] + converted_args + converted_kwargs
93+
return '.'.join(cache_keys)
94+
95+
96+
def _sorted_kwargs_list(kwargs):
97+
"""
98+
Returns a unique and deterministic ordered list from the given kwargs.
99+
"""
100+
sorted_kwargs = sorted(kwargs.items())
101+
sorted_kwargs_list = list(itertools.chain(*sorted_kwargs))
102+
return sorted_kwargs_list

xblocks_contrib/video/content.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# NOTE: Original code has been copied from the following file:
2+
# https://github.com/openedx/edx-platform/blob/farhan/extract-video-xblock/xmodule/contentstore/content.py#L28
3+
4+
5+
class StaticContent: # lint-amnesty, pylint: disable=missing-class-docstring
6+
def __init__(self, loc, name, content_type, data, last_modified_at=None, thumbnail_location=None, import_path=None,
7+
length=None, locked=False, content_digest=None):
8+
self.location = loc
9+
self.name = name # a display string which can be edited, and thus not part of the location which needs to be fixed # lint-amnesty, pylint: disable=line-too-long
10+
self.content_type = content_type
11+
self._data = data
12+
self.length = length
13+
self.last_modified_at = last_modified_at
14+
self.thumbnail_location = thumbnail_location
15+
# optional information about where this file was imported from. This is needed to support import/export
16+
# cycles
17+
self.import_path = import_path
18+
self.locked = locked
19+
self.content_digest = content_digest
20+
21+
@staticmethod
22+
def compute_location(course_key, path, revision=None, is_thumbnail=False): # lint-amnesty, pylint: disable=unused-argument
23+
"""
24+
Constructs a location object for static content.
25+
26+
- course_key: the course that this asset belongs to
27+
- path: is the name of the static asset
28+
- revision: is the object's revision information
29+
- is_thumbnail: is whether or not we want the thumbnail version of this
30+
asset
31+
"""
32+
path = path.replace('/', '_')
33+
return course_key.make_asset_key(
34+
'asset' if not is_thumbnail else 'thumbnail',
35+
AssetLocator.clean_keeping_underscores(path)
36+
).for_branch(None)
37+
38+
@staticmethod
39+
def get_location_from_path(path):
40+
"""
41+
Generate an AssetKey for the given path (old c4x/org/course/asset/name syntax)
42+
"""
43+
try:
44+
return AssetKey.from_string(path)
45+
except InvalidKeyError:
46+
# TODO - re-address this once LMS-11198 is tackled.
47+
if path.startswith('/') or path.endswith('/'):
48+
# try stripping off the leading slash and try again
49+
return AssetKey.from_string(path.strip('/'))
50+
51+
@staticmethod
52+
def serialize_asset_key_with_slash(asset_key):
53+
"""
54+
Legacy code expects the serialized asset key to start w/ a slash; so, do that in one place
55+
:param asset_key:
56+
"""
57+
url = str(asset_key)
58+
if not url.startswith('/'):
59+
url = '/' + url # TODO - re-address this once LMS-11198 is tackled.
60+
return url

0 commit comments

Comments
 (0)