Skip to content

Commit

Permalink
feat: playing with problem v2
Browse files Browse the repository at this point in the history
  • Loading branch information
vzadorozhnii committed May 15, 2024
1 parent eb3842d commit 6e10a80
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 36 deletions.
152 changes: 152 additions & 0 deletions lms/djangoapps/course_home_api/dates/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Dates Tab Views
"""

from django.conf import settings
from edx_django_utils import monitoring as monitoring_utils
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
Expand All @@ -21,8 +22,152 @@
from lms.djangoapps.courseware.masquerade import setup_masquerade
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from xmodule.modulestore.django import modulestore


def enclosing_sequence_for_gating_checks(block):
seq_tags = ['sequential']
if block.location.block_type in seq_tags:
return None

ancestor = block
while ancestor and ancestor.location.block_type not in seq_tags:
ancestor = ancestor.get_parent() # Note: CourseBlock's parent is None

if ancestor:
return block.runtime.get_block(ancestor.location)
return None


def xblock_view_handler(request, xblock, check_if_enrolled=True, disable_staff_debug_info=False):
"""
Helper function to render an XBlock and return the rendered HTML content.
"""
from edx_django_utils.monitoring import set_custom_attribute, set_custom_attributes_for_course_key
from lms.djangoapps.courseware.courses import get_course_with_access
from lms.djangoapps.courseware.block_render import get_block, get_block_by_usage_id, get_block_for_descriptor
from xmodule.modulestore.exceptions import ItemNotFoundError, NoPathToItem
from openedx.features.course_experience.url_helpers import (
get_courseware_url,
get_learning_mfe_home_url,
is_request_from_learning_mfe
)
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
from openedx.features.course_experience.utils import dates_banner_should_display
from lms.djangoapps.courseware.masquerade import is_masquerading_as_specific_student, setup_masquerade
from lms.djangoapps.courseware.views.views import get_optimization_flags_for_content
from lms.djangoapps.edxnotes.helpers import is_feature_enabled
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
from common.djangoapps.edxmako.shortcuts import marketing_link, render_to_response, render_to_string
usage_key = xblock.usage_key

usage_key = usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key))
course_key = usage_key.course_key

# Gathering metrics to make performance measurements easier.
set_custom_attributes_for_course_key(course_key)
set_custom_attribute('usage_key', str(usage_key))
set_custom_attribute('block_type', usage_key.block_type)

staff_access = has_access(request.user, 'staff', course_key)

with modulestore().bulk_operations(course_key):
# verify the user has access to the course, including enrollment check
try:
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=check_if_enrolled)
except:
return None

_course_masquerade, request.user = setup_masquerade(
request,
course_key,
staff_access,
)

UserActivity.record_user_activity(
request.user, usage_key.course_key, request=request, only_if_mobile_app=True
)

recheck_access = request.GET.get('recheck_access') == '1'
try:
block, _ = get_block_by_usage_id(
request,
str(course_key),
str(usage_key),
disable_staff_debug_info=disable_staff_debug_info,
course=course,
will_recheck_access=recheck_access,
)
except:
return

student_view_context = request.GET.dict()
student_view_context['show_bookmark_button'] = request.GET.get('show_bookmark_button', '0') == '1'
student_view_context['show_title'] = request.GET.get('show_title', '1') == '1'

is_learning_mfe = is_request_from_learning_mfe(request)
student_view_context['hide_access_error_blocks'] = is_learning_mfe and recheck_access
is_mobile_app = is_request_from_mobile_app(request)
student_view_context['is_mobile_app'] = is_mobile_app

enable_completion_on_view_service = False
completion_service = block.runtime.service(block, 'completion')
if completion_service and completion_service.completion_tracking_enabled():
if completion_service.blocks_to_mark_complete_on_view({block}):
enable_completion_on_view_service = True
student_view_context['wrap_xblock_data'] = {
'mark-completed-on-view-after-delay': completion_service.get_complete_on_view_delay_ms()
}

missed_deadlines, missed_gated_content = dates_banner_should_display(course_key, request.user)

ancestor_sequence_block = enclosing_sequence_for_gating_checks(block)
if False:
context = {'specific_masquerade': is_masquerading_as_specific_student(request.user, course_key)}
if ancestor_sequence_block.descendants_are_gated(context):
return redirect(
reverse(
'render_xblock',
kwargs={'usage_key_string': str(ancestor_sequence_block.location)}
)
)

if False:
seq_block = ancestor_sequence_block if ancestor_sequence_block else block
if getattr(seq_block, 'is_time_limited', None):
if not _check_sequence_exam_access(request, seq_block.location):
return HttpResponseForbidden("Access to exam content is restricted")

fragment = block.render('student_view', context=student_view_context)
optimization_flags = get_optimization_flags_for_content(block, fragment)

context = {
'fragment': fragment,
'course': course,
'block': block,
'disable_accordion': True,
'allow_iframing': True,
'disable_header': True,
'disable_footer': True,
'disable_window_wrap': True,
'enable_completion_on_view_service': enable_completion_on_view_service,
'edx_notes_enabled': is_feature_enabled(course, request.user),
'staff_access': staff_access,
'xqa_server': settings.FEATURES.get('XQA_SERVER', 'http://your_xqa_server.com'),
'missed_deadlines': missed_deadlines,
'missed_gated_content': missed_gated_content,
'has_ended': course.has_ended(),
'web_app_course_url': get_learning_mfe_home_url(course_key=course.id, url_fragment='home'),
'on_courseware_page': True,
'verified_upgrade_link': verified_upgrade_deadline_link(request.user, course=course),
'is_learning_mfe': is_learning_mfe,
'is_mobile_app': is_mobile_app,
'render_course_wide_assets': True,

**optimization_flags,
}
return render_to_string('courseware/courseware-chromeless.html', context)

class DatesTabView(RetrieveAPIView):
"""
**Use Cases**
Expand Down Expand Up @@ -75,6 +220,13 @@ class DatesTabView(RetrieveAPIView):
def get(self, request, *args, **kwargs):
course_key_string = kwargs.get('course_key_string')
course_key = CourseKey.from_string(course_key_string)
# import pdb; pdb.set_trace()
for xblock in modulestore().get_items(course_key, qualifiers={'category': 'problem'}):
try:
response_xblock = xblock_view_handler(request, xblock)
xblock.update_info_api(response_xblock)
except:
pass

# Enable NR tracing for this view based on course
monitoring_utils.set_custom_attribute('course_id', course_key_string)
Expand Down
3 changes: 3 additions & 0 deletions lms/djangoapps/mobile_api/offline_mode/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Offline mode
"""
158 changes: 126 additions & 32 deletions openedx/features/_mobile_extensions/html_block/mobile_api_module.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import os
import zipfile

from bs4 import BeautifulSoup
Expand All @@ -12,40 +13,118 @@
from xmodule.modulestore.exceptions import ItemNotFoundError


def get_static_file_path(relative_path):
base_path = settings.STATIC_ROOT
return os.path.join(base_path, relative_path)


def read_static_file(path):
with open(path, 'rb') as file:
return file.read()


class HtmlBlockMobileApiMixin:
FILE_NAME = 'content_html.zip'

def update_info_api(self):
def update_info_api(self, html_data=None):
html_data = self.data if not html_data else html_data
if not self.is_modified():
return

base_path = self._base_storage_path()
self.remove_old_files(base_path)

def replace_static_links(match):
link = match.group()
filename = link.split('/static/')[-1]
self.save_asset_file(link, filename)
return f'assets/{filename}'

def replace_iframe(data):
soup = BeautifulSoup(data, 'html.parser')
for node in soup.find_all('iframe'):
replacement = soup.new_tag('p')
tag_a = soup.new_tag('a')
tag_a['href'] = node.get('src')
tag_a.string = node.get('title', node.get('src'))
replacement.append(tag_a)
node.replace_with(replacement)
return str(soup)

pattern = re.compile(r'/static/[\w\+@\-_\.]+')
data = pattern.sub(replace_static_links, self.data)
data = replace_iframe(data)
pattern = re.compile(r'/static/[\w./-]+')
data = pattern.sub(self._replace_static_links, html_data)

# Parse the HTML with BeautifulSoup
soup = BeautifulSoup(data, 'html.parser')
# Replace iframes
self._replace_iframe(soup)
# Add JS bridge script to the HTML data
self._add_js_bridge(soup)
# Convert the modified soup back to a string
data = str(soup)

default_storage.save(f'{base_path}index.html', ContentFile(data))
self.create_zip_file(base_path)

def _replace_static_links(self, match):
link = match.group()
filename = link.split('/static/')[-1]
self.save_asset_file(link, filename)
return f'assets/{filename}'

def _replace_iframe(self, soup):
for node in soup.find_all('iframe'):
replacement = soup.new_tag('p')
tag_a = soup.new_tag('a')
tag_a['href'] = node.get('src')
tag_a.string = node.get('title', node.get('src'))
replacement.append(tag_a)
node.replace_with(replacement)

def _add_js_bridge(self, soup):
import pdb; pdb.set_trace()
script_tag = soup.new_tag('script')
script_tag.string = """
// Function to send messages to iOS
function sendMessageToiOS(message) {
window?.webkit?.messageHandlers?.iOSBridge?.postMessage(message);
}
// Function to send messages to Android
function sendMessageToAndroid(message) {
window?.AndroidBridge?.postMessage(message);
}
// Function to handle messages from iOS
function receiveMessageFromiOS(message) {
console.log("Message received from iOS:", message);
// Handle the message from iOS
}
// Function to handle messages from Android
function receiveMessageFromAndroid(message) {
console.log("Message received from Android:", message);
// Handle the message from Android
}
// Check if iOS bridge is available
if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.iOSBridge) {
// iOS bridge is available
window.addEventListener("messageFromiOS", function(event) {
receiveMessageFromiOS(event.data);
});
}
// Check if Android bridge is available
if (window.AndroidBridge) {
// Android bridge is available
window.addEventListener("messageFromAndroid", function(event) {
receiveMessageFromAndroid(event.data);
});
}
const originalAjax = $.ajax;
$.ajax = function(options) {
sendMessageToiOS(options)
sendMessageToiOS(JSON.stringify(options))
sendMessageToAndroid(options)
sendMessageToAndroid(JSON.stringify(options))
console.log(options, JSON.stringify(options))
return originalAjax.call(this, options);
};
"""

# Insert the script tag before the closing </body> tag
if soup.body:
soup.body.append(script_tag)
else:
# If there's no body tag, add it to the end of the document
soup.append(script_tag)

def remove_old_files(self, base_path):
try:
directories, files = default_storage.listdir(base_path)
Expand All @@ -67,26 +146,40 @@ def _base_storage_path(self):
return '{loc.org}/{loc.course}/{loc.block_type}/{loc.block_id}/'.format(loc=self.location)

def save_asset_file(self, path, filename):
asset_key = StaticContent.get_asset_key_from_path(self.location.course_key, path)
try:
content = AssetManager.find(asset_key)
if '/' in filename:
static_path = get_static_file_path(filename)
content = read_static_file(static_path)
else:
asset_key = StaticContent.get_asset_key_from_path(self.location.course_key, path)
content = AssetManager.find(asset_key).data
except (ItemNotFoundError, NotFoundError):
pass
else:
base_path = self._base_storage_path()
default_storage.save(f'{base_path}assets/{filename}', ContentFile(content.data))
default_storage.save(f'{base_path}assets/{filename}', ContentFile(content))

def create_zip_file(self, base_path):
zf = zipfile.ZipFile(default_storage.path(base_path + self.FILE_NAME), "w")
zf.write(default_storage.path(base_path + "index.html"), "index.html")

try:
directories, files = default_storage.listdir(base_path + 'assets/')
except OSError:
pass
else:
def add_files_to_zip(zip_file, current_base_path, current_path_in_zip):
try:
directories, files = default_storage.listdir(current_base_path)
except OSError:
return

# Add files
for file_name in files:
zf.write(default_storage.path(base_path + 'assets/' + file_name), 'assets/' + file_name)
full_path = os.path.join(current_base_path, file_name)
zip_file.write(full_path, os.path.join(current_path_in_zip, file_name))

# Recursively add directories
for directory in directories:
add_files_to_zip(zip_file, os.path.join(current_base_path, directory),
os.path.join(current_path_in_zip, directory))

add_files_to_zip(zf, default_storage.path(base_path + "assets/"), 'assets')

zf.close()

Expand All @@ -106,7 +199,8 @@ def student_view_data(self):
try:
default_storage.get_created_time(file_path)
except OSError:
self.update_info_api()
# self.update_info_api()
pass

html_data = default_storage.url(file_path)

Expand Down
Loading

0 comments on commit 6e10a80

Please sign in to comment.