Merge pull request #23283 from edx/ndalfonso/AA-12-reset-deadlines
AA-12 reset self paced due dates
This commit is contained in:
@@ -6,8 +6,10 @@
|
||||
|
||||
<%!
|
||||
import json
|
||||
from datetime import date
|
||||
import pytz
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from openedx.core.djangolib.markup import HTML, Text
|
||||
@@ -16,6 +18,7 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
<%
|
||||
course_sections = blocks.get('children')
|
||||
self_paced = context.get('self_paced', False)
|
||||
reset_deadlines_banner_displayed = False
|
||||
%>
|
||||
<main role="main" class="course-outline" id="main" tabindex="-1">
|
||||
% if course_sections is not None:
|
||||
@@ -56,6 +59,12 @@ self_paced = context.get('self_paced', False)
|
||||
scored = 'scored' if subsection.get('scored', False) else ''
|
||||
graded = 'graded' if subsection.get('graded') else ''
|
||||
%>
|
||||
% if not subsection.get('complete', True) and subsection.get('due', timezone.now() + timedelta(1)) < timezone.now() and not reset_deadlines_banner_displayed:
|
||||
<% reset_deadlines_banner_displayed = True %>
|
||||
<script type="text/javascript">
|
||||
$('.reset-deadlines-banner').css('display', 'flex');
|
||||
</script>
|
||||
% endif
|
||||
<li class="subsection accordion ${ 'current' if subsection.get('resume_block') else '' } ${graded} ${scored}">
|
||||
<a
|
||||
% if enable_links:
|
||||
|
||||
@@ -51,6 +51,7 @@ from openedx.core.djangolib.markup import HTML
|
||||
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
|
||||
from openedx.features.course_experience import (
|
||||
COURSE_ENABLE_UNENROLLED_ACCESS_FLAG,
|
||||
RELATIVE_DATES_FLAG,
|
||||
SHOW_REVIEWS_TOOL_FLAG,
|
||||
SHOW_UPGRADE_MSG_ON_COURSE_HOME,
|
||||
UNIFIED_COURSE_TAB_FLAG
|
||||
@@ -948,6 +949,7 @@ class CourseHomeFragmentViewTests(ModuleStoreTestCase):
|
||||
self.course = CourseFactory(
|
||||
start=now() - timedelta(days=30),
|
||||
end=end,
|
||||
self_paced=True,
|
||||
)
|
||||
self.url = course_home_url(self.course)
|
||||
|
||||
@@ -1020,3 +1022,8 @@ class CourseHomeFragmentViewTests(ModuleStoreTestCase):
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assertContains(response, "<span>DISCOUNT_PRICE</span>")
|
||||
|
||||
@override_waffle_flag(RELATIVE_DATES_FLAG, active=True)
|
||||
def test_reset_deadline_banner_is_present_on_course_tab(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertContains(response, '<div class="reset-deadlines-banner">')
|
||||
|
||||
@@ -14,6 +14,7 @@ from completion.test_utils import CompletionWaffleTestMixin
|
||||
from django.contrib.sites.models import Site
|
||||
from django.test import override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from mock import Mock, patch
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
@@ -25,6 +26,8 @@ from waffle.testutils import override_switch
|
||||
from lms.djangoapps.courseware.tests.factories import StaffFactory
|
||||
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
|
||||
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
|
||||
from openedx.core.lib.gating import api as gating_api
|
||||
from openedx.features.course_experience.views.course_outline import (
|
||||
DEFAULT_COMPLETION_TRACKING_START,
|
||||
@@ -55,7 +58,7 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
|
||||
# pylint: disable=super-method-not-called
|
||||
with super(TestCourseOutlinePage, cls).setUpClassAndTestData():
|
||||
cls.courses = []
|
||||
course = CourseFactory.create()
|
||||
course = CourseFactory.create(self_paced=True)
|
||||
with cls.store.bulk_operations(course.id):
|
||||
chapter = ItemFactory.create(category='chapter', parent_location=course.location)
|
||||
sequential = ItemFactory.create(category='sequential', parent_location=chapter.location)
|
||||
@@ -132,6 +135,18 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
|
||||
self.assertContains(response, sequential.format)
|
||||
self.assertTrue(sequential.children)
|
||||
|
||||
def test_reset_course_deadlines(self):
|
||||
course = self.courses[0]
|
||||
enrollment = CourseEnrollment.objects.get(course_id=course.id)
|
||||
ScheduleFactory(
|
||||
start_date=timezone.now() - datetime.timedelta(1),
|
||||
enrollment=enrollment
|
||||
)
|
||||
url = '{}{}'.format(course_home_url(course), 'reset_deadlines')
|
||||
self.client.post(url)
|
||||
updated_schedule = Schedule.objects.get(enrollment=enrollment)
|
||||
self.assertEqual(updated_schedule.start_date.day, datetime.datetime.today().day)
|
||||
|
||||
|
||||
class TestCourseOutlinePageWithPrerequisites(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
|
||||
@@ -7,7 +7,7 @@ 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
|
||||
from .views.course_outline import CourseOutlineFragmentView, reset_course_deadlines
|
||||
from .views.course_reviews import CourseReviewsView
|
||||
from .views.course_sock import CourseSockFragmentView
|
||||
from .views.course_updates import CourseUpdatesFragmentView, CourseUpdatesView
|
||||
@@ -70,4 +70,9 @@ urlpatterns = [
|
||||
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',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -5,11 +5,16 @@ Views to show a course outline.
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import pytz
|
||||
import six
|
||||
|
||||
from completion import waffle as completion_waffle
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import redirect
|
||||
from django.template.context_processors import csrf
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
import edx_when.api as edx_when_api
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from pytz import UTC
|
||||
@@ -17,6 +22,7 @@ from waffle.models import Switch
|
||||
from web_fragments.fragment import Fragment
|
||||
|
||||
from lms.djangoapps.courseware.courses import get_course_overview_with_access
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
|
||||
from student.models import CourseEnrollment
|
||||
from util.milestones_helpers import get_course_content_milestones
|
||||
@@ -154,3 +160,19 @@ 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 = CourseOverview.objects.get(id=course_id)
|
||||
if course.self_paced:
|
||||
enrollment = CourseEnrollment.objects.get(user=request.user, course=course_id)
|
||||
schedule = enrollment.schedule
|
||||
if schedule:
|
||||
schedule.start_date = datetime.datetime.now(pytz.utc)
|
||||
schedule.save()
|
||||
return redirect(reverse('openedx.course_experience.course_home', args=[six.text_type(course_id)]))
|
||||
|
||||
Reference in New Issue
Block a user