In preparation for switching LMS/Studio over from serving legacy courseware URLs in certain places (for example, resume_course_url) to serving learning micro-frontend URLs. TNL-7796
153 lines
6.3 KiB
Python
153 lines
6.3 KiB
Python
"""
|
|
Views for Course Experience API.
|
|
"""
|
|
import logging
|
|
|
|
from django.conf import settings
|
|
from django.urls import reverse
|
|
from django.utils.html import format_html
|
|
from django.utils.translation import ugettext as _
|
|
from eventtracking import tracker
|
|
|
|
from rest_framework.decorators import api_view, authentication_classes, permission_classes
|
|
from rest_framework.exceptions import APIException, ParseError
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
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_api.api import course_detail
|
|
from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active
|
|
from lms.djangoapps.courseware.access import has_access
|
|
from lms.djangoapps.courseware.courses import get_course_with_access
|
|
from lms.djangoapps.courseware.masquerade import is_masquerading, setup_masquerade
|
|
|
|
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.url_helpers import get_learning_mfe_home_url
|
|
from openedx.features.course_experience.utils import dates_banner_should_display
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class UnableToResetDeadlines(APIException):
|
|
status_code = 400
|
|
default_detail = 'Unable to reset deadlines.'
|
|
default_code = 'unable_to_reset_deadlines'
|
|
|
|
|
|
@api_view(['POST'])
|
|
@authentication_classes((
|
|
JwtAuthentication, BearerAuthenticationAllowInactiveUser, SessionAuthenticationAllowInactiveUser,
|
|
))
|
|
@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
|
|
|
|
Request Parameters:
|
|
course_key: course key
|
|
research_event_data: any data that should be included in the research tracking event
|
|
Example: sending the location of where the reset deadlines banner (i.e. outline-tab)
|
|
|
|
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)
|
|
research_event_data = request.data.get('research_event_data', {})
|
|
|
|
# If body doesnt contain 'course_key', return 400 to client.
|
|
if not course_key:
|
|
raise ParseError(_("'course_key' is required."))
|
|
|
|
try:
|
|
course_key = CourseKey.from_string(course_key)
|
|
course_masquerade, user = setup_masquerade(
|
|
request,
|
|
course_key,
|
|
has_access(request.user, 'staff', course_key)
|
|
)
|
|
|
|
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)
|
|
|
|
course_overview = course_detail(request, user.username, course_key)
|
|
# For context here, research_event_data should already contain `location` indicating
|
|
# the page/location dates were reset from and could also contain `block_id` if reset
|
|
# within courseware.
|
|
research_event_data.update({
|
|
'courserun_key': str(course_key),
|
|
'is_masquerading': is_masquerading(user, course_key, course_masquerade),
|
|
'is_staff': has_access(user, 'staff', course_key).has_access,
|
|
'org_key': course_overview.display_org_with_default,
|
|
'user_id': user.id,
|
|
})
|
|
tracker.emit('edx.ui.lms.reset_deadlines.clicked', research_event_data)
|
|
|
|
if course_home_mfe_dates_tab_is_active(course_key):
|
|
body_link = get_learning_mfe_home_url(course_key=str(course_key), view_name='dates')
|
|
else:
|
|
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')),
|
|
'header': _('Your due dates have been successfully shifted to help you stay on track.'),
|
|
'link': body_link,
|
|
'link_text': _('View all dates'),
|
|
'message': _('Deadlines successfully reset.'),
|
|
})
|
|
except Exception as e:
|
|
log.exception(e)
|
|
raise UnableToResetDeadlines # lint-amnesty, pylint: disable=raise-missing-from
|
|
|
|
|
|
class CourseDeadlinesMobileView(RetrieveAPIView):
|
|
"""
|
|
**Use Cases**
|
|
|
|
Request course deadline info for mobile
|
|
|
|
**Example Requests**
|
|
|
|
GET api/course_experience/v1/course_deadlines_info/{course_key}
|
|
|
|
**Response Values**
|
|
|
|
Body consists of the following fields:
|
|
|
|
dates_banner_info: (obj)
|
|
missed_deadlines: (bool) Whether the user has missed any graded content deadlines for the given course.
|
|
missed_gated_content: (bool) Whether the user has missed any gated content for the given course.
|
|
content_type_gating_enabled: (bool) Whether content type gating is enabled for this enrollment.
|
|
verified_upgrade_link: (str) The URL to ecommerce IDA for purchasing the verified upgrade.
|
|
|
|
**Returns**
|
|
|
|
* 200 on success with above fields.
|
|
* 401 if the user is not authenticated.
|
|
* 404 if the course is not available or cannot be seen.
|
|
"""
|
|
|
|
authentication_classes = (
|
|
JwtAuthentication,
|
|
BearerAuthenticationAllowInactiveUser,
|
|
SessionAuthenticationAllowInactiveUser,
|
|
)
|
|
permission_classes = (IsAuthenticated,)
|
|
serializer_class = CourseDeadlinesMobileSerializer
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
course_key_string = kwargs.get('course_key_string')
|
|
course_key = CourseKey.from_string(course_key_string)
|
|
# Although this course data is not used this method will return 404 if course does not exist
|
|
get_course_with_access(request.user, 'load', course_key)
|
|
|
|
serializer = self.get_serializer({})
|
|
return Response(serializer.data)
|