diff --git a/common/djangoapps/util/views.py b/common/djangoapps/util/views.py index 074b690e06..eb1bc9890e 100644 --- a/common/djangoapps/util/views.py +++ b/common/djangoapps/util/views.py @@ -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)) diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py index 75c01de4d9..3f38eaab5e 100644 --- a/lms/djangoapps/courseware/views/index.py +++ b/lms/djangoapps/courseware/views/index.py @@ -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( diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py index d2ea3d7959..15e9efa58b 100644 --- a/lms/djangoapps/courseware/views/views.py +++ b/lms/djangoapps/courseware/views/views.py @@ -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) diff --git a/lms/static/sass/base/_base.scss b/lms/static/sass/base/_base.scss index 61aaf3103b..9fd3d4f6ff 100644 --- a/lms/static/sass/base/_base.scss +++ b/lms/static/sass/base/_base.scss @@ -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 { diff --git a/lms/static/sass/course/layout/_reset_deadlines.scss b/lms/static/sass/course/layout/_reset_deadlines.scss index b931bad5ee..126c737b2a 100644 --- a/lms/static/sass/course/layout/_reset_deadlines.scss +++ b/lms/static/sass/course/layout/_reset_deadlines.scss @@ -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"); diff --git a/lms/static/sass/lms-mobile-rtl.scss b/lms/static/sass/lms-mobile-rtl.scss index 26af472e93..8b01fa6029 100644 --- a/lms/static/sass/lms-mobile-rtl.scss +++ b/lms/static/sass/lms-mobile-rtl.scss @@ -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; + } + } +} diff --git a/lms/static/sass/lms-mobile.scss b/lms/static/sass/lms-mobile.scss index 0d5626de56..7790a47f94 100644 --- a/lms/static/sass/lms-mobile.scss +++ b/lms/static/sass/lms-mobile.scss @@ -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; + } + } +} diff --git a/lms/templates/courseware/courseware-chromeless.html b/lms/templates/courseware/courseware-chromeless.html index 22c18c01d6..4c88b3775c 100644 --- a/lms/templates/courseware/courseware-chromeless.html +++ b/lms/templates/courseware/courseware-chromeless.html @@ -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) %> +% if display_reset_dates_banner: + <%include file="/reset_deadlines_banner.html" /> + +% endif + <%block name="bodyclass">view-in-course view-courseware courseware ${course.css_class or ''} <%block name="title"> % if section_title: diff --git a/lms/templates/reset_deadlines_banner.html b/lms/templates/reset_deadlines_banner.html index 868bcf869d..dc50a27e84 100644 --- a/lms/templates/reset_deadlines_banner.html +++ b/lms/templates/reset_deadlines_banner.html @@ -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> diff --git a/lms/urls.py b/lms/urls.py index a3ec76073f..29e961942e 100644 --- a/lms/urls.py +++ b/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 diff --git a/openedx/features/course_experience/templates/course_experience/mobile/course-dates-fragment.html b/openedx/features/course_experience/templates/course_experience/mobile/course-dates-fragment.html index 5fb3e10f3d..ea53357989 100644 --- a/openedx/features/course_experience/templates/course_experience/mobile/course-dates-fragment.html +++ b/openedx/features/course_experience/templates/course_experience/mobile/course-dates-fragment.html @@ -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 diff --git a/openedx/features/course_experience/tests/views/test_course_outline.py b/openedx/features/course_experience/tests/views/test_course_outline.py index 9114682918..81fe570151 100644 --- a/openedx/features/course_experience/tests/views/test_course_outline.py +++ b/openedx/features/course_experience/tests/views/test_course_outline.py @@ -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) diff --git a/openedx/features/course_experience/urls.py b/openedx/features/course_experience/urls.py index 1a444ab99a..eca0e988ea 100644 --- a/openedx/features/course_experience/urls.py +++ b/openedx/features/course_experience/urls.py @@ -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, ), ] diff --git a/openedx/features/course_experience/utils.py b/openedx/features/course_experience/utils.py index 057511535d..7b93c2170e 100644 --- a/openedx/features/course_experience/utils.py +++ b/openedx/features/course_experience/utils.py @@ -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 diff --git a/openedx/features/course_experience/views/course_dates.py b/openedx/features/course_experience/views/course_dates.py index 6ffa66bc99..6b69e4f0a0 100644 --- a/openedx/features/course_experience/views/course_dates.py +++ b/openedx/features/course_experience/views/course_dates.py @@ -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 diff --git a/openedx/features/course_experience/views/course_outline.py b/openedx/features/course_experience/views/course_outline.py index deac9b5cea..61abbc926b 100644 --- a/openedx/features/course_experience/views/course_outline.py +++ b/openedx/features/course_experience/views/course_outline.py @@ -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)]))