Fixes for Shift deadlines during masquerade in Learning MFE

The change to masquerade in the courseware view allows the proper
viewing of the xblock from the perspective of the masqueraded user.
In this case, it allows a staff user masquerading as a learner to see
their shift dates calls to action inside the MFE (the old view already
had this set up). The second change allows the staff user masquerading
to reset the schedule of the learner being masqueraded via the CTAs
This commit is contained in:
Dillon Dumesnil
2020-12-16 17:11:38 +00:00
parent 79fa6c515f
commit e1517223b9
4 changed files with 76 additions and 14 deletions

View File

@@ -196,6 +196,9 @@ 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
IMPORTANT NOTE: If updates are happening to the logic here, ALSO UPDATE the `reset_course_deadlines`
function in openedx/features/course_experience/api/v1/views.py as well.
"""
course_key = CourseKey.from_string(request.POST.get('course_id'))
_course_masquerade, user = setup_masquerade(

View File

@@ -1631,6 +1631,9 @@ def render_xblock(request, usage_key_string, check_if_enrolled=True):
u"Rendering of the xblock view '{}' is not supported.".format(bleach.clean(requested_view, strip=True))
)
staff_access = has_access(request.user, 'staff', course_key)
_course_masquerade, request.user = setup_masquerade(request, course_key, staff_access)
with modulestore().bulk_operations(course_key):
# verify the user has access to the course, including enrollment check
try:
@@ -1668,7 +1671,7 @@ def render_xblock(request, usage_key_string, check_if_enrolled=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': bool(request.user.has_perm(VIEW_XQA_INTERFACE, course)),
'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,

View File

@@ -1,23 +1,29 @@
"""
Tests for reset deadlines endpoint.
"""
import datetime
import ddt
from django.urls import reverse
from django.utils import timezone
from mock import patch
from common.djangoapps.course_modes.models import CourseMode
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
from common.djangoapps.student.models import CourseEnrollment
from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
from openedx.core.djangoapps.schedules.models import Schedule
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
from xmodule.modulestore.tests.factories import CourseFactory
@ddt.ddt
class ResetCourseDeadlinesViewTests(BaseCourseHomeTests):
class ResetCourseDeadlinesViewTests(BaseCourseHomeTests, MasqueradeMixin):
"""
Tests for reset deadlines endpoint.
"""
@ddt.data(CourseMode.VERIFIED)
def test_reset_deadlines(self, enrollment_mode):
CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode)
def test_reset_deadlines(self):
CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED)
# Test correct post body
response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id})
self.assertEqual(response.status_code, 200)
@@ -30,6 +36,32 @@ class ResetCourseDeadlinesViewTests(BaseCourseHomeTests):
)
self.assertEqual(response.status_code, 400)
def test_reset_deadlines_with_masquerade(self):
""" Staff users should be able to masquerade as a learner and reset the learner's schedule """
course = CourseFactory.create(self_paced=True)
student_username = self.user.username
student_enrollment = CourseEnrollment.enroll(self.user, course.id)
student_schedule = ScheduleFactory.create(
start_date=timezone.now() - datetime.timedelta(days=100),
enrollment=student_enrollment
)
staff_schedule = ScheduleFactory(
start_date=timezone.now() - datetime.timedelta(days=30),
enrollment__course__id=course.id,
enrollment__user=self.staff_user,
)
self.switch_to_staff()
self.update_masquerade(course=course, username=student_username)
with patch('openedx.features.course_experience.api.v1.views.dates_banner_should_display',
return_value=(True, False)):
self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': course.id})
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)
self.assertEqual(updated_staff_schedule.start_date, staff_schedule.start_date)
def test_post_unauthenticated_user(self):
self.client.logout()
response = self.client.post(reverse('course-experience-reset-course-deadlines'), {'course_key': self.course.id})

View File

@@ -1,4 +1,7 @@
import six
"""
Views for Course Experience API.
"""
import logging
from django.conf import settings
from django.urls import reverse
@@ -13,15 +16,20 @@ from rest_framework.generics import RetrieveAPIView
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from opaque_keys.edx.keys import CourseKey
from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active
from lms.djangoapps.course_home_api.utils import get_microfrontend_url
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.courses import get_course_with_access
from lms.djangoapps.courseware.masquerade import setup_masquerade
from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.schedules.utils import reset_self_paced_schedule
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.features.course_experience.api.v1.serializers import CourseDeadlinesMobileSerializer
from openedx.features.course_experience.utils import dates_banner_should_display
log = logging.getLogger(__name__)
class UnableToResetDeadlines(APIException):
@@ -36,6 +44,13 @@ class UnableToResetDeadlines(APIException):
))
@permission_classes((IsAuthenticated,))
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
IMPORTANT NOTE: If updates are happening to the logic here, ALSO UPDATE the `reset_course_deadlines`
function in common/djangoapps/util/views.py as well.
"""
course_key = request.data.get('course_key', None)
# If body doesnt contain 'course_key', return 400 to client.
@@ -47,13 +62,21 @@ def reset_course_deadlines(request):
raise ParseError(_("Only 'course_key' is expected."))
try:
reset_self_paced_schedule(request.user, course_key)
course_key = CourseKey.from_string(course_key)
_course_masquerade, user = setup_masquerade(
request,
course_key,
has_access(request.user, 'staff', course_key)
)
key = CourseKey.from_string(course_key)
if course_home_mfe_dates_tab_is_active(key):
body_link = get_microfrontend_url(course_key=course_key, view_name='dates')
missed_deadlines, missed_gated_content = dates_banner_should_display(course_key, user)
if missed_deadlines and not missed_gated_content:
reset_self_paced_schedule(user, course_key)
if course_home_mfe_dates_tab_is_active(course_key):
body_link = get_microfrontend_url(course_key=str(course_key), view_name='dates')
else:
body_link = '{}{}'.format(settings.LMS_ROOT_URL, reverse('dates', args=[six.text_type(course_key)]))
body_link = '{}{}'.format(settings.LMS_ROOT_URL, reverse('dates', args=[str(course_key)]))
return Response({
'body': format_html('<a href="{}">{}</a>', body_link, _('View all dates')),
@@ -62,7 +85,8 @@ def reset_course_deadlines(request):
'link_text': _('View all dates'),
'message': _('Deadlines successfully reset.'),
})
except Exception:
except Exception as e:
log.exception(e)
raise UnableToResetDeadlines