AA-85 mobile reset dates
- render reset dates banner in webview for mobile app. - improve banner redirect mechanism
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
|
||||
|
||||
import ast
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
@@ -10,10 +11,15 @@ import crum
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import Http404, HttpResponse, HttpResponseForbidden, HttpResponseServerError
|
||||
from django.views.decorators.csrf import requires_csrf_token
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie, requires_csrf_token
|
||||
from django.views.defaults import server_error
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from lms.djangoapps.courseware.masquerade import setup_masquerade
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from openedx.core.djangoapps.schedules.utils import reset_self_paced_schedule
|
||||
from six.moves import map
|
||||
|
||||
import track.views
|
||||
@@ -187,3 +193,34 @@ def add_p3p_header(view_func):
|
||||
response['P3P'] = settings.P3P_HEADER
|
||||
return response
|
||||
return inner
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def reset_course_deadlines(request):
|
||||
"""
|
||||
Set the start_date of a schedule to today, which in turn will adjust due dates for
|
||||
sequentials belonging to a self paced course
|
||||
"""
|
||||
from lms.urls import RENDER_XBLOCK_NAME
|
||||
from openedx.features.course_experience.urls import COURSE_HOME_VIEW_NAME
|
||||
|
||||
detail_id_dict = ast.literal_eval(request.POST.get('reset_deadlines_redirect_url_id_dict'))
|
||||
print('***************', detail_id_dict)
|
||||
redirect_url = request.POST.get('reset_deadlines_redirect_url_base', COURSE_HOME_VIEW_NAME)
|
||||
course_key = CourseKey.from_string(detail_id_dict['course_id'])
|
||||
masquerade_details, masquerade_user = setup_masquerade(
|
||||
request,
|
||||
course_key,
|
||||
has_access(request.user, 'staff', course_key)
|
||||
)
|
||||
if masquerade_details and masquerade_details.role == 'student' and masquerade_details.user_name and (
|
||||
redirect_url == COURSE_HOME_VIEW_NAME
|
||||
):
|
||||
# Masquerading as a specific student, so reset that student's schedule
|
||||
user = masquerade_user
|
||||
else:
|
||||
user = request.user
|
||||
reset_self_paced_schedule(user, course_key)
|
||||
if redirect_url == RENDER_XBLOCK_NAME:
|
||||
detail_id_dict.pop('course_id')
|
||||
return redirect(reverse(redirect_url, kwargs=detail_id_dict))
|
||||
|
||||
@@ -51,7 +51,9 @@ from openedx.features.course_experience import (
|
||||
default_course_url_name,
|
||||
RELATIVE_DATES_FLAG,
|
||||
)
|
||||
from openedx.features.course_experience.urls import COURSE_HOME_VIEW_NAME
|
||||
from openedx.features.course_experience.utils import get_course_outline_block_tree
|
||||
from openedx.features.course_experience.utils import reset_deadlines_banner_should_display
|
||||
from openedx.features.course_experience.views.course_sock import CourseSockFragmentView
|
||||
from openedx.features.enterprise_support.api import data_sharing_consent_required
|
||||
from shoppingcart.models import CourseRegistrationCode
|
||||
@@ -450,6 +452,8 @@ class CoursewareIndex(View):
|
||||
Returns and creates the rendering context for the courseware.
|
||||
Also returns the table of contents for the courseware.
|
||||
"""
|
||||
from lms.urls import RESET_COURSE_DEADLINES_NAME
|
||||
|
||||
course_url_name = default_course_url_name(self.course.id)
|
||||
course_url = reverse(course_url_name, kwargs={'course_id': six.text_type(self.course.id)})
|
||||
show_search = (
|
||||
@@ -458,31 +462,14 @@ class CoursewareIndex(View):
|
||||
)
|
||||
staff_access = self.is_staff
|
||||
|
||||
reset_deadlines_url = reverse(
|
||||
'openedx.course_experience.reset_course_deadlines', kwargs={'course_id': six.text_type(self.course.id)}
|
||||
)
|
||||
|
||||
allow_anonymous = allow_public_access(self.course, [COURSE_VISIBILITY_PUBLIC])
|
||||
display_reset_dates_banner = False
|
||||
if not allow_anonymous and RELATIVE_DATES_FLAG.is_enabled(self.course.id): # pylint: disable=too-many-nested-blocks
|
||||
course_overview = CourseOverview.objects.get(id=str(self.course_key))
|
||||
end_date = getattr(course_overview, 'end_date')
|
||||
if course_overview.self_paced and (not end_date or timezone.now() < end_date):
|
||||
if (CourseEnrollment.objects.filter(
|
||||
course=course_overview, user=request.user, mode=CourseMode.VERIFIED
|
||||
).exists()):
|
||||
course_block_tree = get_course_outline_block_tree(
|
||||
request, str(self.course_key), request.user
|
||||
)
|
||||
course_sections = course_block_tree.get('children', [])
|
||||
for section in course_sections:
|
||||
if display_reset_dates_banner:
|
||||
break
|
||||
for subsection in section.get('children', []):
|
||||
if (not subsection.get('complete', True)
|
||||
and subsection.get('due', timezone.now() + timedelta(1)) < timezone.now()):
|
||||
display_reset_dates_banner = True
|
||||
break
|
||||
if not allow_anonymous and RELATIVE_DATES_FLAG.is_enabled(self.course.id):
|
||||
display_reset_dates_banner = reset_deadlines_banner_should_display(self.course_key, request)
|
||||
|
||||
reset_deadlines_url = reverse(RESET_COURSE_DEADLINES_NAME) if display_reset_dates_banner else None
|
||||
|
||||
reset_deadlines_redirect_url_base = COURSE_HOME_VIEW_NAME if reset_deadlines_url else None
|
||||
|
||||
courseware_context = {
|
||||
'csrf': csrf(self.request)['csrf_token'],
|
||||
@@ -506,8 +493,10 @@ class CoursewareIndex(View):
|
||||
'disable_accordion': COURSE_OUTLINE_PAGE_FLAG.is_enabled(self.course.id),
|
||||
'show_search': show_search,
|
||||
'relative_dates_is_enabled': RELATIVE_DATES_FLAG.is_enabled(self.course.id),
|
||||
'reset_deadlines_url': reset_deadlines_url,
|
||||
'display_reset_dates_banner': display_reset_dates_banner,
|
||||
'reset_deadlines_url': reset_deadlines_url,
|
||||
'reset_deadlines_redirect_url_base': reset_deadlines_redirect_url_base,
|
||||
'reset_deadlines_redirect_url_id_dict': {'course_id': str(self.course.id)},
|
||||
}
|
||||
courseware_context.update(
|
||||
get_experiment_user_metadata_context(
|
||||
|
||||
@@ -115,6 +115,7 @@ from openedx.features.course_experience import (
|
||||
RELATIVE_DATES_FLAG,
|
||||
)
|
||||
from openedx.features.course_experience.course_tools import CourseToolsPluginManager
|
||||
from openedx.features.course_experience.utils import reset_deadlines_banner_should_display
|
||||
from openedx.features.course_experience.views.course_dates import CourseDatesFragmentView
|
||||
from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML
|
||||
from openedx.features.course_experience.waffle import waffle as course_experience_waffle
|
||||
@@ -717,6 +718,9 @@ class CourseTabView(EdxFragmentView):
|
||||
"""
|
||||
Creates the context for the fragment's template.
|
||||
"""
|
||||
from lms.urls import RESET_COURSE_DEADLINES_NAME
|
||||
from openedx.features.course_experience.urls import COURSE_HOME_VIEW_NAME
|
||||
|
||||
can_masquerade = request.user.has_perm(MASQUERADE_AS_STUDENT, course)
|
||||
supports_preview_menu = tab.get('supports_preview_menu', False)
|
||||
uses_bootstrap = self.uses_bootstrap(request, course, tab=tab)
|
||||
@@ -731,10 +735,6 @@ class CourseTabView(EdxFragmentView):
|
||||
else:
|
||||
masquerade = None
|
||||
|
||||
reset_deadlines_url = reverse(
|
||||
'openedx.course_experience.reset_course_deadlines', kwargs={'course_id': text_type(course.id)}
|
||||
)
|
||||
|
||||
display_reset_dates_banner = False
|
||||
if RELATIVE_DATES_FLAG.is_enabled(course.id):
|
||||
course_overview = CourseOverview.get_from_id(course.id)
|
||||
@@ -744,6 +744,10 @@ class CourseTabView(EdxFragmentView):
|
||||
).exists()):
|
||||
display_reset_dates_banner = True
|
||||
|
||||
reset_deadlines_url = reverse(RESET_COURSE_DEADLINES_NAME) if display_reset_dates_banner else None
|
||||
|
||||
reset_deadlines_redirect_url_base = COURSE_HOME_VIEW_NAME if reset_deadlines_url else None
|
||||
|
||||
context = {
|
||||
'course': course,
|
||||
'tab': tab,
|
||||
@@ -754,8 +758,10 @@ class CourseTabView(EdxFragmentView):
|
||||
'uses_bootstrap': uses_bootstrap,
|
||||
'uses_pattern_library': not uses_bootstrap,
|
||||
'disable_courseware_js': True,
|
||||
'reset_deadlines_url': reset_deadlines_url,
|
||||
'display_reset_dates_banner': display_reset_dates_banner,
|
||||
'reset_deadlines_url': reset_deadlines_url,
|
||||
'reset_deadlines_redirect_url_base': reset_deadlines_redirect_url_base,
|
||||
'reset_deadlines_redirect_url_id_dict': {'course_id': str(course.id)}
|
||||
}
|
||||
# Avoid Multiple Mathjax loading on the 'user_profile'
|
||||
if 'profile_page_context' in kwargs:
|
||||
@@ -1614,11 +1620,14 @@ def _track_successful_certificate_generation(user_id, course_id):
|
||||
@ensure_valid_usage_key
|
||||
@xframe_options_exempt
|
||||
@transaction.non_atomic_requests
|
||||
@ensure_csrf_cookie
|
||||
def render_xblock(request, usage_key_string, check_if_enrolled=True):
|
||||
"""
|
||||
Returns an HttpResponse with HTML content for the xBlock with the given usage_key.
|
||||
The returned HTML is a chromeless rendering of the xBlock (excluding content of the containing courseware).
|
||||
"""
|
||||
from lms.urls import RENDER_XBLOCK_NAME, RESET_COURSE_DEADLINES_NAME
|
||||
|
||||
usage_key = UsageKey.from_string(usage_key_string)
|
||||
|
||||
usage_key = usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key))
|
||||
@@ -1655,6 +1664,14 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
|
||||
'mark-completed-on-view-after-delay': completion_service.get_complete_on_view_delay_ms()
|
||||
}
|
||||
|
||||
display_reset_dates_banner = False
|
||||
if RELATIVE_DATES_FLAG.is_enabled(course.id):
|
||||
display_reset_dates_banner = reset_deadlines_banner_should_display(course_key, request)
|
||||
|
||||
reset_deadlines_url = reverse(RESET_COURSE_DEADLINES_NAME) if display_reset_dates_banner else None
|
||||
|
||||
reset_deadlines_redirect_url_base = RENDER_XBLOCK_NAME if reset_deadlines_url else None
|
||||
|
||||
context = {
|
||||
'fragment': block.render('student_view', context=student_view_context),
|
||||
'course': course,
|
||||
@@ -1667,6 +1684,10 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
|
||||
'edx_notes_enabled': is_feature_enabled(course, request.user),
|
||||
'staff_access': bool(request.user.has_perm(VIEW_XQA_INTERFACE, course)),
|
||||
'xqa_server': settings.FEATURES.get('XQA_SERVER', 'http://your_xqa_server.com'),
|
||||
'display_reset_dates_banner': display_reset_dates_banner,
|
||||
'reset_deadlines_url': reset_deadlines_url,
|
||||
'reset_deadlines_redirect_url_base': reset_deadlines_redirect_url_base,
|
||||
'reset_deadlines_redirect_url_id_dict': {'course_id': str(course.id), 'usage_key_string': usage_key_string}
|
||||
}
|
||||
return render_to_response('courseware/courseware-chromeless.html', context)
|
||||
|
||||
|
||||
@@ -316,7 +316,7 @@ div.reset-deadlines-banner {
|
||||
color: theme-color("inverse");
|
||||
padding-top: 10px;
|
||||
margin-right: 10px;
|
||||
flex: 0 0 auto;
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
|
||||
form {
|
||||
|
||||
@@ -7,7 +7,7 @@ div.reset-deadlines-banner {
|
||||
|
||||
div,
|
||||
button {
|
||||
flex: 0 0 auto;
|
||||
flex: 0 1 auto;
|
||||
|
||||
&.reset-deadlines-text {
|
||||
color: theme-color("inverse");
|
||||
|
||||
@@ -3,3 +3,28 @@
|
||||
@import 'bourbon/bourbon';
|
||||
@import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages
|
||||
@import 'build-mobile';
|
||||
|
||||
.reset-deadlines-banner {
|
||||
background-color: theme-color("primary");
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 15px 20px;
|
||||
margin-top: 5px;
|
||||
|
||||
div,
|
||||
button {
|
||||
flex: 0 1 auto;
|
||||
|
||||
&.reset-deadlines-text {
|
||||
color: theme-color("inverse");
|
||||
padding-top: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.reset-deadlines-button {
|
||||
color: #0075b4;
|
||||
background-color: theme-color("inverse");
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,28 @@
|
||||
@import 'bourbon/bourbon';
|
||||
@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages
|
||||
@import 'build-mobile';
|
||||
|
||||
.reset-deadlines-banner {
|
||||
background-color: theme-color("primary");
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 15px 20px;
|
||||
margin-top: 5px;
|
||||
|
||||
div,
|
||||
button {
|
||||
flex: 0 1 auto;
|
||||
|
||||
&.reset-deadlines-text {
|
||||
color: theme-color("inverse");
|
||||
padding-top: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&.reset-deadlines-button {
|
||||
color: #0075b4;
|
||||
background-color: theme-color("inverse");
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,13 @@ from openedx.core.djangolib.js_utils import js_escaped_string
|
||||
<% return _("{course_number} Courseware").format(course_number=course.display_number_with_default) %>
|
||||
</%def>
|
||||
|
||||
% if display_reset_dates_banner:
|
||||
<%include file="/reset_deadlines_banner.html" />
|
||||
<script type="text/javascript">
|
||||
$('.reset-deadlines-banner').css('display', 'flex');
|
||||
</script>
|
||||
% endif
|
||||
|
||||
<%block name="bodyclass">view-in-course view-courseware courseware ${course.css_class or ''}</%block>
|
||||
<%block name="title"><title>
|
||||
% if section_title:
|
||||
|
||||
@@ -8,6 +8,8 @@ from django.utils.translation import ugettext as _
|
||||
<div class="reset-deadlines-text">${_("It looks like you've missed some important deadlines. Reset your deadlines and get started today.")}</div>
|
||||
<form method="post" action="${reset_deadlines_url}">
|
||||
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
|
||||
<input type="hidden" name="reset_deadlines_redirect_url_base" value="${reset_deadlines_redirect_url_base}">
|
||||
<input type="hidden" name="reset_deadlines_redirect_url_id_dict" value="${reset_deadlines_redirect_url_id_dict}">
|
||||
<button class="btn reset-deadlines-button">${_("Reset my deadlines")}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
12
lms/urls.py
12
lms/urls.py
@@ -55,6 +55,9 @@ from staticbook import views as staticbook_views
|
||||
from student import views as student_views
|
||||
from util import views as util_views
|
||||
|
||||
RESET_COURSE_DEADLINES_NAME = 'reset_course_deadlines'
|
||||
RENDER_XBLOCK_NAME = 'render_xblock'
|
||||
|
||||
if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
|
||||
django_autodiscover()
|
||||
admin.site.site_header = _('LMS Administration')
|
||||
@@ -237,6 +240,7 @@ COURSE_URLS = [
|
||||
name='registration_code_details',
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns += [
|
||||
# jump_to URLs for direct access to a location in the course
|
||||
url(
|
||||
@@ -291,7 +295,7 @@ urlpatterns += [
|
||||
url(
|
||||
r'^xblock/{usage_key_string}$'.format(usage_key_string=settings.USAGE_KEY_PATTERN),
|
||||
courseware_views.render_xblock,
|
||||
name='render_xblock',
|
||||
name=RENDER_XBLOCK_NAME,
|
||||
),
|
||||
|
||||
# xblock Resource URL
|
||||
@@ -316,6 +320,12 @@ urlpatterns += [
|
||||
# TODO: These views need to be updated before they work
|
||||
url(r'^calculate$', util_views.calculate),
|
||||
|
||||
url(
|
||||
r'^reset_deadlines$',
|
||||
util_views.reset_course_deadlines,
|
||||
name=RESET_COURSE_DEADLINES_NAME,
|
||||
),
|
||||
|
||||
url(r'^courses/?$', branding_views.courses, name='courses'),
|
||||
|
||||
# About the course
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
<%namespace name="static" file="../../static_content.html"/>
|
||||
|
||||
% if display_reset_dates_banner:
|
||||
<%include file="/reset_deadlines_banner.html" />
|
||||
% endif
|
||||
% for course_date in course_date_blocks:
|
||||
<%include file="../dates-summary.html" args="course_date=course_date"/>
|
||||
% endfor
|
||||
|
||||
@@ -24,6 +24,7 @@ from waffle.models import Switch
|
||||
from waffle.testutils import override_switch
|
||||
|
||||
from lms.djangoapps.courseware.tests.factories import StaffFactory
|
||||
from lms.urls import RESET_COURSE_DEADLINES_NAME
|
||||
from gating import api as lms_gating_api
|
||||
from lms.djangoapps.course_api.blocks.transformers.milestones import MilestonesAndSpecialExamsTransformer
|
||||
from openedx.core.djangoapps.schedules.models import Schedule
|
||||
@@ -170,8 +171,8 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
|
||||
start_date=timezone.now() - datetime.timedelta(1),
|
||||
enrollment=enrollment
|
||||
)
|
||||
url = '{}{}'.format(course_home_url(course), 'reset_deadlines')
|
||||
self.client.post(url)
|
||||
post_dict = {'reset_deadlines_redirect_url_id_dict': json.dumps({'course_id': str(course.id)})}
|
||||
self.client.post(reverse(RESET_COURSE_DEADLINES_NAME), post_dict)
|
||||
updated_schedule = Schedule.objects.get(enrollment=enrollment)
|
||||
self.assertEqual(updated_schedule.start_date.date(), datetime.datetime.today().date())
|
||||
|
||||
@@ -204,8 +205,8 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
url = '{}{}'.format(course_home_url(course), 'reset_deadlines')
|
||||
self.client.post(url)
|
||||
post_dict = {'reset_deadlines_redirect_url_id_dict': json.dumps({'course_id': str(course.id)})}
|
||||
self.client.post(reverse(RESET_COURSE_DEADLINES_NAME), post_dict)
|
||||
updated_schedule = Schedule.objects.get(id=student_schedule.id)
|
||||
self.assertEqual(updated_schedule.start_date.date(), datetime.datetime.today().date())
|
||||
updated_staff_schedule = Schedule.objects.get(id=staff_schedule.id)
|
||||
@@ -240,8 +241,8 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
url = '{}{}'.format(course_home_url(course), 'reset_deadlines')
|
||||
self.client.post(url)
|
||||
post_dict = {'reset_deadlines_redirect_url_id_dict': json.dumps({'course_id': str(course.id)})}
|
||||
self.client.post(reverse(RESET_COURSE_DEADLINES_NAME), post_dict)
|
||||
updated_student_schedule = Schedule.objects.get(id=student_schedule.id)
|
||||
self.assertEqual(updated_student_schedule.start_date, student_schedule.start_date)
|
||||
updated_staff_schedule = Schedule.objects.get(id=staff_schedule.id)
|
||||
|
||||
@@ -7,18 +7,21 @@ from django.conf.urls import url
|
||||
|
||||
from .views.course_dates import CourseDatesFragmentMobileView
|
||||
from .views.course_home import CourseHomeFragmentView, CourseHomeView
|
||||
from .views.course_outline import CourseOutlineFragmentView, reset_course_deadlines
|
||||
from .views.course_outline import CourseOutlineFragmentView
|
||||
from .views.course_reviews import CourseReviewsView
|
||||
from .views.course_sock import CourseSockFragmentView
|
||||
from .views.course_updates import CourseUpdatesFragmentView, CourseUpdatesView
|
||||
from .views.latest_update import LatestUpdateFragmentView
|
||||
from .views.welcome_message import WelcomeMessageFragmentView, dismiss_welcome_message
|
||||
|
||||
COURSE_HOME_VIEW_NAME = 'openedx.course_experience.course_home'
|
||||
COURSE_DATES_FRAGMENT_VIEW_NAME = 'openedx.course_experience.mobile_dates_fragment_view'
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^$',
|
||||
CourseHomeView.as_view(),
|
||||
name='openedx.course_experience.course_home',
|
||||
name=COURSE_HOME_VIEW_NAME,
|
||||
),
|
||||
url(
|
||||
r'^updates$',
|
||||
@@ -68,11 +71,6 @@ urlpatterns = [
|
||||
url(
|
||||
r'^mobile_dates_fragment',
|
||||
CourseDatesFragmentMobileView.as_view(),
|
||||
name='openedx.course_experience.mobile_dates_fragment_view',
|
||||
),
|
||||
url(
|
||||
r'^reset_deadlines$',
|
||||
reset_course_deadlines,
|
||||
name='openedx.course_experience.reset_course_deadlines',
|
||||
name=COURSE_DATES_FRAGMENT_VIEW_NAME,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -3,13 +3,20 @@ Common utilities for the course experience, including course outline.
|
||||
"""
|
||||
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from completion.models import BlockCompletion
|
||||
from django.utils import timezone
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from six.moves import range
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from lms.djangoapps.course_api.blocks.api import get_blocks
|
||||
from lms.djangoapps.course_blocks.utils import get_student_module_as_dict
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.lib.cache_utils import request_cached
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@@ -232,3 +239,36 @@ def get_resume_block(block):
|
||||
if resume_block:
|
||||
return resume_block
|
||||
return block
|
||||
|
||||
|
||||
def reset_deadlines_banner_should_display(course_key, request):
|
||||
"""
|
||||
Return whether or not the reset banner should display,
|
||||
determined by whether or not a course has any past-due,
|
||||
incomplete sequentials
|
||||
"""
|
||||
display_reset_dates_banner = False
|
||||
course_overview = CourseOverview.objects.get(id=str(course_key))
|
||||
course_end_date = getattr(course_overview, 'end_date', None)
|
||||
is_self_paced = getattr(course_overview, 'self_paced', False)
|
||||
is_course_staff = bool(
|
||||
request.user and course_overview and has_access(request.user, 'staff', course_overview, course_overview.id)
|
||||
)
|
||||
if is_self_paced and (not is_course_staff) and (not course_end_date or timezone.now() < course_end_date):
|
||||
if (CourseEnrollment.objects.filter(
|
||||
course=course_overview, user=request.user, mode=CourseMode.VERIFIED
|
||||
).exists()):
|
||||
course_block_tree = get_course_outline_block_tree(
|
||||
request, str(course_key), request.user
|
||||
)
|
||||
course_sections = course_block_tree.get('children', [])
|
||||
for section in course_sections:
|
||||
if display_reset_dates_banner:
|
||||
break
|
||||
for subsection in section.get('children', []):
|
||||
if (not subsection.get('complete', True)
|
||||
and subsection.get('due', timezone.now() + timedelta(1)) < timezone.now()):
|
||||
display_reset_dates_banner = True
|
||||
break
|
||||
|
||||
return display_reset_dates_banner
|
||||
|
||||
@@ -5,9 +5,14 @@ Fragment for rendering the course dates sidebar.
|
||||
|
||||
from django.http import Http404
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.utils.translation import get_language_bidi
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from web_fragments.fragment import Fragment
|
||||
from openedx.features.course_experience import RELATIVE_DATES_FLAG
|
||||
from openedx.features.course_experience.utils import reset_deadlines_banner_should_display
|
||||
|
||||
from lms.djangoapps.courseware.courses import get_course_date_blocks, get_course_with_access
|
||||
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
|
||||
@@ -49,10 +54,10 @@ class CourseDatesFragmentMobileView(CourseDatesFragmentView):
|
||||
"""
|
||||
template_name = 'course_experience/mobile/course-dates-fragment.html'
|
||||
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
def get(self, request, *args, **kwargs):
|
||||
if not request.user.is_authenticated:
|
||||
raise Http404
|
||||
print('****************', CourseDatesFragmentMobileView.__dict__)
|
||||
return super(CourseDatesFragmentMobileView, self).get(request, *args, **kwargs)
|
||||
|
||||
def css_dependencies(self):
|
||||
@@ -67,3 +72,37 @@ class CourseDatesFragmentMobileView(CourseDatesFragmentView):
|
||||
return self.get_css_dependencies('style-mobile-rtl')
|
||||
else:
|
||||
return self.get_css_dependencies('style-mobile')
|
||||
|
||||
def render_to_fragment(self, request, course_id=None, **kwargs):
|
||||
"""
|
||||
Render the course dates fragment.
|
||||
"""
|
||||
from lms.urls import RESET_COURSE_DEADLINES_NAME
|
||||
from openedx.features.course_experience.urls import COURSE_DATES_FRAGMENT_VIEW_NAME
|
||||
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False)
|
||||
course_date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=2)
|
||||
|
||||
display_reset_dates_banner = False
|
||||
|
||||
if RELATIVE_DATES_FLAG.is_enabled(course.id):
|
||||
display_reset_dates_banner = reset_deadlines_banner_should_display(course_key, request)
|
||||
|
||||
reset_deadlines_url = reverse(RESET_COURSE_DEADLINES_NAME) if display_reset_dates_banner else None
|
||||
|
||||
reset_deadlines_redirect_url_base = COURSE_DATES_FRAGMENT_VIEW_NAME if (
|
||||
reset_deadlines_url) else None
|
||||
|
||||
context = {
|
||||
'course_date_blocks': [block for block in course_date_blocks if block.title != 'current_datetime'],
|
||||
'display_reset_dates_banner': display_reset_dates_banner,
|
||||
'reset_deadlines_url': reset_deadlines_url,
|
||||
'reset_deadlines_redirect_url_base': reset_deadlines_redirect_url_base,
|
||||
'reset_deadlines_redirect_url_id_dict': {'course_id': course_id}
|
||||
}
|
||||
html = render_to_string(self.template_name, context)
|
||||
dates_fragment = Fragment(html)
|
||||
self.add_fragment_resource_urls(dates_fragment)
|
||||
|
||||
return dates_fragment
|
||||
|
||||
@@ -160,24 +160,3 @@ class CourseOutlineFragmentView(EdxFragmentView):
|
||||
if children:
|
||||
children[0]['resume_block'] = True
|
||||
self.mark_first_unit_to_resume(children[0])
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def reset_course_deadlines(request, course_id):
|
||||
"""
|
||||
Set the start_date of a schedule to today, which in turn will adjust due dates for
|
||||
sequentials belonging to a self paced course
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
masquerade_details, masquerade_user = setup_masquerade(
|
||||
request,
|
||||
course_key,
|
||||
has_access(request.user, 'staff', course_key)
|
||||
)
|
||||
if masquerade_details and masquerade_details.role == 'student' and masquerade_details.user_name:
|
||||
# Masquerading as a specific student, so reset that student's schedule
|
||||
user = masquerade_user
|
||||
else:
|
||||
user = request.user
|
||||
reset_self_paced_schedule(user, course_key)
|
||||
return redirect(reverse('openedx.course_experience.course_home', args=[six.text_type(course_key)]))
|
||||
|
||||
Reference in New Issue
Block a user