Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Niedielnitsev ivan/offline problem media #2565

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3795130
feat: [AXM-24] Update structure for course enrollments API (#2515)
KyryloKireiev Mar 19, 2024
a6ecd16
feat: [AXM-47] Add course_status field to primary object (#2517)
KyryloKireiev Mar 22, 2024
bbc5a01
feat: [AXM-40] add courses progress to enrollment endpoint (#2519)
NiedielnitsevIvan Mar 22, 2024
c2881dc
feat: [AXM-53] add assertions for primary course (#2522)
NiedielnitsevIvan Apr 2, 2024
d0cb091
feat: [AXM-200] Implement user's enrolments status API (#2530)
KyryloKireiev Apr 8, 2024
a32b144
feat: [AXM-33] create enrollments filtering by course completion stat…
NiedielnitsevIvan Apr 8, 2024
8f9affd
feat: [AXM-236] Add progress for other courses (#2536)
NiedielnitsevIvan Apr 10, 2024
ebcdde6
fix: [AXM-277] Change _get_last_visited_block_path_and_unit_name meth…
KyryloKireiev Apr 16, 2024
18f5eb8
feat: [AXM-297] Add progress to assignments in BlocksInfoInCourseView…
KyryloKireiev Apr 25, 2024
cdfa6fa
feat: [AXM-288] Change response to represent Future assignments the s…
KyryloKireiev Apr 29, 2024
97392f5
feat: [AXM-252] add settings for edx-ace push notifications (#2541)
NiedielnitsevIvan Apr 29, 2024
874e9c4
feat: [AXM-271] Add push notification event to discussions (#2548)
NiedielnitsevIvan Apr 29, 2024
47dc395
feat: [AXM-287,310,331] Change course progress calculation logic (#2553)
KyryloKireiev May 13, 2024
e5697e7
feat: [AXM-373] Add push notification event about course invitations …
NiedielnitsevIvan May 14, 2024
0d02744
refactor: [AXM-475] refactor firebase settings (#2560)
NiedielnitsevIvan May 22, 2024
4cfab2d
feat: [AXM-556] refactor discussion push notifications sending
NiedielnitsevIvan May 29, 2024
036f989
fix: fix typo
NiedielnitsevIvan May 29, 2024
b8639e3
test: [AXM-556] add topic_id to tests
NiedielnitsevIvan May 29, 2024
ed4db49
Merge pull request #2562 from raccoongang/NiedielnitsevIvan/AXM-556/f…
NiedielnitsevIvan May 29, 2024
7628201
feat: [ICNC-597] Added downloading functionality for HTML
Sep 14, 2021
8b8faf1
feat: [ICNC-597] Implemented video downloading
Sep 15, 2021
aa38b2e
feat: playing with problem v2
vzadorozhnii May 15, 2024
585cf54
feat: move logic into mobile_api
vzadorozhnii May 16, 2024
e269490
feat: last update
vzadorozhnii May 20, 2024
dbfe6a1
feat: offline mode app
vzadorozhnii May 21, 2024
074c5bd
feat: new offline mode state
vzadorozhnii May 24, 2024
81c0ec3
chore: add fixme's
NiedielnitsevIvan May 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions common/djangoapps/student/models/course_enrollment.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,71 @@ class UnenrollmentNotAllowed(CourseEnrollmentException):
pass


class CourseEnrollmentQuerySet(models.QuerySet):
"""
Custom queryset for CourseEnrollment with Table-level filter methods.
"""

def active(self):
"""
Returns a queryset of CourseEnrollment objects for courses that are currently active.
"""
return self.filter(is_active=True)

def without_certificates(self, user_username):
"""
Returns a queryset of CourseEnrollment objects for courses that do not have a certificate.
"""
from lms.djangoapps.certificates.models import GeneratedCertificate # pylint: disable=import-outside-toplevel
course_ids_with_certificates = GeneratedCertificate.objects.filter(
user__username=user_username
).values_list('course_id', flat=True)
return self.exclude(course_id__in=course_ids_with_certificates)

def with_certificates(self, user_username):
"""
Returns a queryset of CourseEnrollment objects for courses that have a certificate.
"""
from lms.djangoapps.certificates.models import GeneratedCertificate # pylint: disable=import-outside-toplevel
course_ids_with_certificates = GeneratedCertificate.objects.filter(
user__username=user_username
).values_list('course_id', flat=True)
return self.filter(course_id__in=course_ids_with_certificates)

def in_progress(self, user_username, time_zone=UTC):
"""
Returns a queryset of CourseEnrollment objects for courses that are currently in progress.
"""
now = datetime.now(time_zone)
return self.active().without_certificates(user_username).filter(
Q(course__start__lte=now, course__end__gte=now)
| Q(course__start__isnull=True, course__end__isnull=True)
| Q(course__start__isnull=True, course__end__gte=now)
| Q(course__start__lte=now, course__end__isnull=True),
)

def completed(self, user_username):
"""
Returns a queryset of CourseEnrollment objects for courses that have been completed.
"""
return self.active().with_certificates(user_username)

def expired(self, user_username, time_zone=UTC):
"""
Returns a queryset of CourseEnrollment objects for courses that have expired.
"""
now = datetime.now(time_zone)
return self.active().without_certificates(user_username).filter(course__end__lt=now)


class CourseEnrollmentManager(models.Manager):
"""
Custom manager for CourseEnrollment with Table-level filter methods.
"""

def get_queryset(self):
return CourseEnrollmentQuerySet(self.model, using=self._db)

def is_small_course(self, course_id):
"""
Returns false if the number of enrollments are one greater than 'max_enrollments' else true
Expand Down
8 changes: 5 additions & 3 deletions lms/djangoapps/course_api/blocks/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
from datetime import datetime
from unittest import mock
from unittest.mock import Mock
from unittest.mock import MagicMock, Mock
from urllib.parse import urlencode, urlunparse

import ddt
Expand Down Expand Up @@ -209,8 +209,9 @@ def test_not_authenticated_public_course_with_all_blocks(self):
self.query_params['all_blocks'] = True
self.verify_response(403)

@mock.patch('lms.djangoapps.mobile_api.course_info.serializers.get_course_assignments', return_value=[])
@mock.patch("lms.djangoapps.course_api.blocks.forms.permissions.is_course_public", Mock(return_value=True))
def test_not_authenticated_public_course_with_blank_username(self):
def test_not_authenticated_public_course_with_blank_username(self, get_course_assignment_mock: MagicMock) -> None:
"""
Verify behaviour when accessing course blocks of a public course for anonymous user anonymously.
"""
Expand Down Expand Up @@ -368,7 +369,8 @@ def test_extra_field_when_not_requested(self):
block_data['type'] == 'course'
)

def test_data_researcher_access(self):
@mock.patch('lms.djangoapps.mobile_api.course_info.serializers.get_course_assignments', return_value=[])
def test_data_researcher_access(self, get_course_assignment_mock: MagicMock) -> None:
"""
Test if data researcher has access to the api endpoint
"""
Expand Down
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
8 changes: 6 additions & 2 deletions lms/djangoapps/courseware/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import logging
from collections import defaultdict, namedtuple
from datetime import datetime
from datetime import datetime, timedelta

import six
import pytz
Expand Down Expand Up @@ -587,7 +587,7 @@ def get_course_blocks_completion_summary(course_key, user):


@request_cached()
def get_course_assignments(course_key, user, include_access=False): # lint-amnesty, pylint: disable=too-many-statements
def get_course_assignments(course_key, user, include_access=False, include_without_due=False,): # lint-amnesty, pylint: disable=too-many-statements
"""
Returns a list of assignment (at the subsection/sequential level) due dates for the given course.

Expand All @@ -607,6 +607,10 @@ def get_course_assignments(course_key, user, include_access=False): # lint-amne
for subsection_key in block_data.get_children(section_key):
due = block_data.get_xblock_field(subsection_key, 'due')
graded = block_data.get_xblock_field(subsection_key, 'graded', False)

if not due and include_without_due:
due = now + timedelta(days=1000)

if due and graded:
first_component_block_id = get_first_component_of_block(subsection_key, block_data)
contains_gated_content = include_access and block_data.get_xblock_field(
Expand Down
10 changes: 10 additions & 0 deletions lms/djangoapps/discussion/signals/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Signal handlers related to discussions.
"""

import six
import logging

from django.conf import settings
Expand All @@ -23,6 +24,7 @@
from openedx.core.djangoapps.django_comment_common import signals
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
from openedx.core.djangoapps.theming.helpers import get_current_site
from lms.djangoapps.offline_mode.utils.xblock_helpers import get_xblock_view_response, generate_request_with_service_user

log = logging.getLogger(__name__)

Expand All @@ -47,6 +49,13 @@ def update_discussions_on_course_publish(sender, course_key, **kwargs): # pylin
countdown=settings.DISCUSSION_SETTINGS['COURSE_PUBLISH_TASK_DELAY'],
)

import pdb;
pdb.set_trace()
request = generate_request_with_service_user()
# FIXME: Change the block id to the actual block id
result = get_xblock_view_response(request, 'block-v1:new+123+new+type@problem+block@f7693d5dde094f65a28485582125936d', 'student_view')
print(result)


@receiver(signals.comment_created)
def send_discussion_email_notification(sender, user, post,
Expand Down Expand Up @@ -113,6 +122,7 @@ def create_message_context(comment, site):
'course_id': str(thread.course_id),
'comment_id': comment.id,
'comment_body': comment.body,
'comment_body_text': comment.body_text,
'comment_author_id': comment.user_id,
'comment_created_at': comment.created_at, # comment_client models dates are already serialized
'thread_id': thread.id,
Expand Down
Loading
Loading